Repository: JelteF/derive_more
Pull request #206 (2022-10-08 21:02:15)

Add try/ref versions to unwrap
I extended Unwrap to add one that returns Option, one that returns reference, and a combination of the two, for my use.
It's ready to merge, but I don't think it's well discussed and considered. `try_unwrap_<variant>_ref` is... little uncool.
So I'll leave it as a draft to solicit opinions.

```rust
#[derive(Unwrap)]
enum Number {
    Integer(i64),
    Decimal(f64),
    #[unwrap(ref)] // this affects only `FromString`
    FromString(String),
}

#[derive(Unwrap)]
#[unwrap(ref)] // this affects the entire enum,
               // and adds `unwrap_<name>_ref` and `try_unwrap_<name>_ref`.
enum Maybe<T> {
    Just(T),
    Nothing,
}

fn main() {
    assert_eq!(Maybe::Just(1).unwrap_just(), 1);
    assert_eq!(Maybe::Just(2).try_unwrap_just(), Some(2));
    assert_eq!(Maybe::Nothing.try_unwrap_just(), None);
    assert_eq!(*(&Maybe::Just(42)).unwrap_just_ref(), 42);
}
```

This relates to #186.
`var_a` looks better than `try_unwrap_var_a_ref`. But I think in that case it probably should not be implemented in Unwrap.
Initially, I added it in Unwrap because it was tedious, but maybe it should be split into TryUnwrap, UnwrapRef, and TryUnwrapRef.

Is there anything else I have to do to be merged?

rassvet2: There is a known issue with *_ref naming conflicts when an enum has SomeVariant and SomeVariantRef.
I need advice on this as well.
JelteF: I didn't look at the code in detail, but a few thoughts on the general design:
1. For completeness this should also add `unwrap_xxx_mut` versions (unless somehow hard to do) 
2. I really don't like the `try_unwrap` prefix. Some other options I can think of: `as_thing`, `try_into_thing`, `try_thing`. I think I like `try_into_thing` the best. (I will continue calling it `try_unwrap` in the rest of this comment. 
3. Should `try_unwrap` return an error instead?


This seems rare enough that i wouldn't consider it a problem:
> There is a known issue with *_ref naming conflicts when an enum has SomeVariant and SomeVariantRef.
JelteF: To clarify: The most open questions and discussion points for me are around the `try_unwrap` version of this derive. I think it makes sense to have the `unwrap_thing_ref` and `unwrap_thing_mut` without any further discussion on the design.
rassvet2: for 1 and 2:

Yes, I will. I like the shortness of `as`, but it seems misleading. Looking at std, it seems that functions starting with `as_` rarely return `Option` or `Result`. So I'm going to use `try_into`.

for 3 and 4:

Makes sense. I just forgot that `TryInto` returns `Result`, not `Option`. I will make it returns the original value as an error for now.
As for `try_unwrap`, I'm going to delete it temporarily and separate it into a new PR.
JelteF: @rassvet2 are you still planning on making your suggested changes? 
rassvet2: I'm sorry I left this hanging for so long.
I will work on this. It may take about two weeks.

rassvet2: It's ready for review. Finally, I decided like:
- `try_unwrap_*` was renamed to `try_into_*` and moved to `TryIntoVariant` (Isn't it a bit long compared to Unwrap?)
- Both `Unwrap` and `TryIntoVariant` basically generate `owned` versions only, but `ref` and `ref_mut` can also be generated by specifying them in the attribute.
- `try_into_*` returns a `Result` value. This will be `Ok` if the conversion succeeds, and `Err` if it fails. `Ok` contains the converted value, and `Err` contains the original value.
  This makes it useful when you do not want to lose ownership if conversion fails.
rassvet2: I'm wondering again if I should rename `TryIntoVariant`. Now I don't feel it's a good name. Since these functions don't convert one enum variant into another, they're not a conversion like `std::convert`. In that respect, `Unwrap` was a good name as it "unwraps" an enum variant into a tuple of its fields.
However, I can't think of anything that fits. Do you agree with `TryIntoVariant`, or not?
JelteF: >  Since these functions don't convert one enum variant into another, they're not a conversion like std::convert

That's a really good point. I think you're right that TryUnwrap is probably the best name. Both `Rc` and `Arc` have a `try_unwrap` method too that does exactly this, so there's also prior art from `std` on this naming. I'm sorry for the back and forth on the naming.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12c2c17..50a206b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Add `FromStr` derive support for enums that contain variants without fields.
   If you pass the name of the variant to `from_str` it will create the matching
   variant.
+- Add `#[unwrap(owned, ref, ref_mut)]` attribute for the `Unwrap` derive.
+  By using them, it is possible to derive implementations for the reference types as well.
+  ([#206](https://github.com/JelteF/derive_more/pull/206))
+- Add `TryUnwrap` derive similar to the `Unwrap` derive. This one returns a `Result` and does not panic.
+  ([#206](https://github.com/JelteF/derive_more/pull/206))
 
 ### Changed
 
@@ -49,7 +54,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
   should prevent code style linters from attempting to modify the generated
   code.
 
-
 ### Fixed
 
 - Use a deterministic `HashSet` in all derives, this is needed for rust analyzer
diff --git a/Cargo.toml b/Cargo.toml
index 1438c14..e17fc54 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -65,6 +65,7 @@ sum = ["derive_more-impl/sum"]
 try_into = ["derive_more-impl/try_into"]
 is_variant = ["derive_more-impl/is_variant"]
 unwrap = ["derive_more-impl/unwrap"]
+try_unwrap = ["derive_more-impl/try_unwrap"]
 
 std = []
 full = [
@@ -92,6 +93,7 @@ full = [
     "sum",
     "try_into",
     "unwrap",
+    "try_unwrap",
 ]
 
 testing-helpers = ["derive_more-impl/testing-helpers"]
@@ -216,6 +218,11 @@ name = "unwrap"
 path = "tests/unwrap.rs"
 required-features = ["unwrap"]
 
+[[test]]
+name = "try_unwrap"
+path = "tests/try_unwrap.rs"
+required-features = ["try_unwrap"]
+
 [[test]]
 name = "compile_fail"
 path = "tests/compile_fail/mod.rs"
diff --git a/README.md b/README.md
index 1c9d43d..9ef4be1 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,7 @@ These don't derive traits, but derive static methods instead.
    out the [`derive-new`] crate.
 2. [`IsVariant`], for each variant `foo` of an enum type, derives a `is_foo` method.
 3. [`Unwrap`], for each variant `foo` of an enum type, derives an `unwrap_foo` method.
+4. [`TryUnwrap`], for each variant `foo` of an enum type, derives an `try_unwrap_foo` method.
 
 
 
@@ -205,3 +206,4 @@ extern crate derive_more;
 [`Constructor`]: https://jeltef.github.io/derive_more/derive_more/constructor.html
 [`IsVariant`]: https://jeltef.github.io/derive_more/derive_more/is_variant.html
 [`Unwrap`]: https://jeltef.github.io/derive_more/derive_more/unwrap.html
+[`TryUnwrap`]: https://jeltef.github.io/derive_more/derive_more/try_unwrap.html
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index 38dcf97..f3b23f0 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -32,7 +32,7 @@ unicode-xid = { version = "0.2.2", optional = true }
 rustc_version = { version = "0.4", optional = true }
 
 [dev-dependencies]
-derive_more = { path = "..", features = ["add", "debug", "from_str", "not", "try_into"] }
+derive_more = { path = "..", features = ["add", "debug", "from_str", "not", "try_into", "try_unwrap"] }
 itertools = "0.10.5"
 
 [badges]
@@ -68,5 +68,6 @@ sum = []
 try_into = ["syn/extra-traits"]
 is_variant = ["dep:convert_case"]
 unwrap = ["dep:convert_case"]
+try_unwrap = ["dep:convert_case"]
 
 testing-helpers = ["dep:rustc_version"]
diff --git a/impl/doc/try_unwrap.md b/impl/doc/try_unwrap.md
new file mode 100644
index 0000000..f6ec49e
--- /dev/null
+++ b/impl/doc/try_unwrap.md
@@ -0,0 +1,80 @@
+# What `#[derive(TryUnwrap)]` generates
+
+This works almost like `Unwrap`.
+When an enum is decorated with `#[derive(TryUnwrap)]`, for each variant `foo` in the enum, with fields `(a, b, c, ...)` a public instance method `try_unwrap_foo(self) -> Result<(a, b, c, ...), TryUnwrapError<Self>>` is generated.
+If you don't want the `try_unwrap_foo` method generated for a variant, you can put the `#[try_unwrap(ignore)]` attribute on that variant.
+If you want to treat a reference, you can put the `#[try_unwrap(ref)]` attribute on the enum declaration or that variant, then `try_unwrap_foo_ref(self) -> Result<(&a, &b, &c, ...), TryUnwrapError<&Self>>` will be generated. You can also use mutable references by putting `#[unwrap(ref_mut)]`.
+However, unlike `Unwrap`, it does not panic if the conversion fails. Also, values that fail to convert are not dropped but returned as an `Err`.
+
+## Example usage
+
+```rust
+# use derive_more::TryUnwrap;
+# 
+# #[derive(Debug, PartialEq)]
+#[derive(TryUnwrap)]
+#[try_unwrap(ref)]
+enum Maybe<T> {
+    Nothing,
+    Just(T),
+}
+
+fn main() {
+    assert_eq!(Maybe::Just(1).try_unwrap_just(), Ok(1));
+
+    // Unlike `Unwrap`, it does not panic.
+    assert_eq!(
+        Maybe::<()>::Nothing.try_unwrap_just().map_err(|err| err.input),
+        Err(Maybe::<()>::Nothing), // and the value is returned!
+    );
+    assert_eq!(
+        Maybe::Just(2).try_unwrap_nothing().map_err(|err| err.input),
+        Err(Maybe::Just(2)),
+    );
+    assert_eq!(
+        Maybe::<()>::Nothing.try_unwrap_just().map_err(|err| err.to_string()),
+        Err("Attempt to call `Maybe::try_unwrap_just()` on a `Maybe::Nothing` value".into()),
+    );
+
+    assert_eq!((&Maybe::Just(42)).try_unwrap_just_ref(), Ok(&42));
+}
+```
+
+### What is generated?
+
+The derive in the above example code generates the following code:
+```rust
+# use derive_more::TryUnwrapError;
+#
+# enum Maybe<T> {
+#     Just(T),
+#     Nothing,
+# }
+#
+impl<T> Maybe<T> {
+    pub fn try_unwrap_nothing(self) -> Result<(), TryUnwrapError<Self>> {
+        match self {
+            Maybe::Nothing => Ok(()),
+            val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
+        }
+    }
+    pub fn try_unwrap_nothing_ref(&self) -> Result<(), TryUnwrapError<&Self>> {
+        match self {
+            Maybe::Nothing => Ok(()),
+            val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
+        }
+    }
+    pub fn try_unwrap_just(self) -> Result<T, TryUnwrapError<Self>> {
+        match self {
+            Maybe::Just(field_0) => Ok(field_0),
+            val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
+        }
+    }
+    pub fn try_unwrap_just_ref(&self) -> Result<&T, TryUnwrapError<&Self>> {
+        match self {
+            Maybe::Just(field_0) => Ok(field_0),
+            val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
+        }
+    }
+}
+```
diff --git a/impl/doc/unwrap.md b/impl/doc/unwrap.md
index 5a1abd5..e1a1034 100644
--- a/impl/doc/unwrap.md
+++ b/impl/doc/unwrap.md
@@ -1,9 +1,8 @@
 # What `#[derive(Unwrap)]` generates
 
-When an enum is decorated with `#[derive(Unwrap)]`, for each variant `foo` in the enum,
-with fields `(a, b, c, ...)` a public instance method `unwrap_foo(self) -> (a, b, c, ...)`
-is generated. If you don't want the `unwrap_foo` method generated for a variant,
-you can put the `#[unwrap(ignore)]` attribute on that variant.
+When an enum is decorated with `#[derive(Unwrap)]`, for each variant `foo` in the enum, with fields `(a, b, c, ...)` a public instance method `unwrap_foo(self) -> (a, b, c, ...)` is generated.
+If you don't want the `unwrap_foo` method generated for a variant, you can put the `#[unwrap(ignore)]` attribute on that variant.
+If you want to treat a reference, you can put the `#[unwrap(ref)]` attribute on the enum declaration or that variant, then `unwrap_foo_ref(self) -> (&a, &b, &c, ...)` will be generated. You can also use mutable references by putting `#[unwrap(ref_mut)]`.
 
 
 
@@ -12,12 +11,24 @@ you can put the `#[unwrap(ignore)]` attribute on that variant.
 
 ```rust
 # use derive_more::Unwrap;
-#
+# 
+# #[derive(Debug, PartialEq)]
 #[derive(Unwrap)]
+#[unwrap(ref)]
 enum Maybe<T> {
     Just(T),
     Nothing,
 }
+
+fn main() {
+    assert_eq!(Maybe::Just(1).unwrap_just(), 1);
+
+    // Panics if variants are different
+    // assert_eq!(Maybe::<()>::Nothing.unwrap_just(), /* panic */);
+    // assert_eq!(Maybe::Just(2).unwrap_nothing(), /* panic */);
+
+    assert_eq!((&Maybe::Just(42)).unwrap_just_ref(), &42);
+}
 ```
 
 
@@ -29,19 +40,30 @@ The derive in the above example code generates the following code:
 #     Just(T),
 #     Nothing,
 # }
+#
 impl<T> Maybe<T> {
-    pub fn unwrap_just(self) -> (T) {
+    pub fn unwrap_nothing(self) -> () {
         match self {
-            Maybe::Just(field_0) => (field_0),
-            Maybe::Nothing => panic!(concat!("called `", stringify!(Maybe), "::", stringify!(unwrap_just),
-                                     "()` on a `", stringify!(Nothing), "` value"))
+            Maybe::Nothing => (),
+            _ => panic!(),
         }
     }
-    pub fn unwrap_nothing(self) -> () {
+    pub fn unwrap_nothing_ref(&self) -> () {
         match self {
             Maybe::Nothing => (),
-            Maybe::Just(..) => panic!(concat!("called `", stringify!(Maybe), "::", stringify!(unwrap_nothing),
-                                     "()` on a `", stringify!(Just), "` value"))
+            _ => panic!(),
+        }
+    }
+    pub fn unwrap_just(self) -> T {
+        match self {
+            Maybe::Just(field_0) => field_0,
+            _ => panic!(),
+        }
+    }
+    pub fn unwrap_just_ref(&self) -> &T {
+        match self {
+            Maybe::Just(field_0) => field_0,
+            _ => panic!(),
         }
     }
 }
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 394eeb4..1760086 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -61,6 +61,8 @@ mod not_like;
 mod sum_like;
 #[cfg(feature = "try_into")]
 mod try_into;
+#[cfg(feature = "try_unwrap")]
+mod try_unwrap;
 #[cfg(feature = "unwrap")]
 mod unwrap;
 
@@ -259,3 +261,10 @@ create_derive!(
 );
 
 create_derive!("unwrap", unwrap, Unwrap, unwrap_derive, unwrap);
+create_derive!(
+    "try_unwrap",
+    try_unwrap,
+    TryUnwrap,
+    try_unwrap_derive,
+    try_unwrap,
+);
diff --git a/impl/src/try_unwrap.rs b/impl/src/try_unwrap.rs
new file mode 100644
index 0000000..2fd27ec
--- /dev/null
+++ b/impl/src/try_unwrap.rs
@@ -0,0 +1,167 @@
+use crate::utils::{AttrParams, DeriveType, State};
+use convert_case::{Case, Casing};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use syn::{DeriveInput, Fields, Ident, Result, Type};
+
+pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
+    let state = State::with_attr_params(
+        input,
+        trait_name,
+        quote! {},
+        "try_unwrap".into(),
+        AttrParams {
+            enum_: vec!["ignore", "owned", "ref", "ref_mut"],
+            variant: vec!["ignore", "owned", "ref", "ref_mut"],
+            struct_: vec!["ignore"],
+            field: vec!["ignore"],
+        },
+    )?;
+    assert!(
+        state.derive_type == DeriveType::Enum,
+        "TryUnwrap can only be derived for enums"
+    );
+
+    let enum_name = &input.ident;
+    let (imp_generics, type_generics, where_clause) = input.generics.split_for_impl();
+
+    let variant_data = state.enabled_variant_data();
+
+    let mut funcs = vec![];
+    for (variant_state, info) in
+        Iterator::zip(variant_data.variant_states.iter(), variant_data.infos)
+    {
+        let variant = variant_state.variant.unwrap();
+        let fn_name = format_ident!(
+            "try_unwrap_{ident}",
+            ident = variant.ident.to_string().to_case(Case::Snake),
+            span = variant.ident.span(),
+        );
+        let ref_fn_name = format_ident!(
+            "try_unwrap_{ident}_ref",
+            ident = variant.ident.to_string().to_case(Case::Snake),
+            span = variant.ident.span(),
+        );
+        let mut_fn_name = format_ident!(
+            "try_unwrap_{ident}_mut",
+            ident = variant.ident.to_string().to_case(Case::Snake),
+            span = variant.ident.span(),
+        );
+        let variant_ident = &variant.ident;
+        let (data_pattern, ret_value, data_types) = get_field_info(&variant.fields);
+        let pattern = quote! { #enum_name :: #variant_ident #data_pattern };
+
+        let (failed_block, failed_block_ref, failed_block_mut) = (
+            failed_block(&state, enum_name, &fn_name),
+            failed_block(&state, enum_name, &ref_fn_name),
+            failed_block(&state, enum_name, &mut_fn_name),
+        );
+
+        let doc_owned = format!(
+            "Attempts to unwrap this value to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_ref = format!(
+            "Attempts to unwrap this reference to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_mut = format!(
+            "Attempts to unwrap this mutable reference to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_else = "Returns a [TryUnwrapError] with the original value if this value is of any other type.";
+        let func = quote! {
+            #[inline]
+            #[track_caller]
+            #[doc = #doc_owned]
+            #[doc = #doc_else]
+            pub fn #fn_name(self) -> Result<(#(#data_types),*), ::derive_more::TryUnwrapError<Self>> {
+                match self {
+                    #pattern => Ok(#ret_value),
+                    val @ _ => #failed_block,
+                }
+            }
+        };
+
+        let ref_func = quote! {
+            #[inline]
+            #[track_caller]
+            #[doc = #doc_ref]
+            #[doc = #doc_else]
+            pub fn #ref_fn_name(&self) -> Result<(#(&#data_types),*), ::derive_more::TryUnwrapError<&Self>> {
+                match self {
+                    #pattern => Ok(#ret_value),
+                    val @ _ => #failed_block_ref,
+                }
+            }
+        };
+
+        let mut_func = quote! {
+            #[inline]
+            #[track_caller]
+            #[doc = #doc_mut]
+            #[doc = #doc_else]
+            pub fn #mut_fn_name(&mut self) -> Result<(#(&mut #data_types),*), ::derive_more::TryUnwrapError<&mut Self>> {
+                match self {
+                    #pattern => Ok(#ret_value),
+                    val @ _ => #failed_block_mut,
+                }
+            }
+        };
+
+        if info.owned && state.default_info.owned {
+            funcs.push(func);
+        }
+        if info.ref_ && state.default_info.ref_ {
+            funcs.push(ref_func);
+        }
+        if info.ref_mut && state.default_info.ref_mut {
+            funcs.push(mut_func);
+        }
+    }
+
+    let imp = quote! {
+        #[automatically_derived]
+        impl #imp_generics #enum_name #type_generics #where_clause {
+            #(#funcs)*
+        }
+    };
+
+    Ok(imp)
+}
+
+fn get_field_info(fields: &Fields) -> (TokenStream, TokenStream, Vec<&Type>) {
+    match fields {
+        Fields::Named(_) => panic!("cannot unwrap anonymous records"),
+        Fields::Unnamed(ref fields) => {
+            let (idents, types) = fields
+                .unnamed
+                .iter()
+                .enumerate()
+                .map(|(n, it)| (format_ident!("field_{n}"), &it.ty))
+                .unzip::<_, _, Vec<_>, Vec<_>>();
+            (quote! { (#(#idents),*) }, quote! { (#(#idents),*) }, types)
+        }
+        Fields::Unit => (quote! {}, quote! { () }, vec![]),
+    }
+}
+
+fn failed_block(state: &State, enum_name: &Ident, func_name: &Ident) -> TokenStream {
+    let arms = state
+        .variant_states
+        .iter()
+        .map(|it| it.variant.unwrap())
+        .map(|variant| {
+            let data_pattern = match variant.fields {
+                Fields::Named(_) => quote! { {..} },
+                Fields::Unnamed(_) => quote! { (..) },
+                Fields::Unit => quote! {},
+            };
+            let variant_ident = &variant.ident;
+        let error = quote! { ::derive_more::TryUnwrapError::<_>::new(val, stringify!(#enum_name), stringify!(#variant_ident), stringify!(#func_name)) };
+            quote! { val @ #enum_name :: #variant_ident #data_pattern => Err(#error) }
+        });
+
+    quote! {
+        match val {
+            #(#arms),*
+        }
+    }
+}
diff --git a/impl/src/unwrap.rs b/impl/src/unwrap.rs
index 61cec4f..d1b9643 100644
--- a/impl/src/unwrap.rs
+++ b/impl/src/unwrap.rs
@@ -2,7 +2,7 @@ use crate::utils::{AttrParams, DeriveType, State};
 use convert_case::{Case, Casing};
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
-use syn::{DeriveInput, Fields, Result};
+use syn::{DeriveInput, Fields, Ident, Result, Type};
 
 pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
     let state = State::with_attr_params(
@@ -11,8 +11,8 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStre
         quote! {},
         "unwrap".into(),
         AttrParams {
-            enum_: vec!["ignore"],
-            variant: vec!["ignore"],
+            enum_: vec!["ignore", "owned", "ref", "ref_mut"],
+            variant: vec!["ignore", "owned", "ref", "ref_mut"],
             struct_: vec!["ignore"],
             field: vec!["ignore"],
         },
@@ -25,65 +25,97 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStre
     let enum_name = &input.ident;
     let (imp_generics, type_generics, where_clause) = input.generics.split_for_impl();
 
+    let variant_data = state.enabled_variant_data();
+
     let mut funcs = vec![];
-    for variant_state in state.enabled_variant_data().variant_states {
+    for (variant_state, info) in
+        Iterator::zip(variant_data.variant_states.iter(), variant_data.infos)
+    {
         let variant = variant_state.variant.unwrap();
         let fn_name = format_ident!(
-            "unwrap_{}",
-            variant.ident.to_string().to_case(Case::Snake),
+            "unwrap_{ident}",
+            ident = variant.ident.to_string().to_case(Case::Snake),
+            span = variant.ident.span(),
+        );
+        let ref_fn_name = format_ident!(
+            "unwrap_{ident}_ref",
+            ident = variant.ident.to_string().to_case(Case::Snake),
+            span = variant.ident.span(),
+        );
+        let mut_fn_name = format_ident!(
+            "unwrap_{ident}_mut",
+            ident = variant.ident.to_string().to_case(Case::Snake),
             span = variant.ident.span(),
         );
         let variant_ident = &variant.ident;
+        let (data_pattern, ret_value, data_types) = get_field_info(&variant.fields);
+        let pattern = quote! { #enum_name :: #variant_ident #data_pattern };
+
+        let (failed_block, failed_block_ref, failed_block_mut) = (
+            failed_block(&state, enum_name, &fn_name),
+            failed_block(&state, enum_name, &ref_fn_name),
+            failed_block(&state, enum_name, &mut_fn_name),
+        );
+
+        let doc_owned = format!(
+            "Unwraps this value to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_ref = format!(
+            "Unwraps this reference to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_mut = format!(
+            "Unwraps this mutable reference to the `{enum_name}::{variant_ident}` variant.\n",
+        );
+        let doc_else = "Panics if this value is of any other type.";
 
-        let (data_pattern, ret_value, ret_type) = match variant.fields {
-            Fields::Named(_) => panic!("cannot unwrap anonymous records"),
-            Fields::Unnamed(ref fields) => {
-                let data_pattern =
-                    (0..fields.unnamed.len()).fold(vec![], |mut a, n| {
-                        a.push(format_ident!("field_{n}"));
-                        a
-                    });
-                let ret_type = &fields.unnamed;
-                (
-                    quote! { (#(#data_pattern),*) },
-                    quote! { (#(#data_pattern),*) },
-                    quote! { (#ret_type) },
-                )
+        let func = quote! {
+            #[inline]
+            #[track_caller]
+            #[doc = #doc_owned]
+            #[doc = #doc_else]
+            pub fn #fn_name(self) -> (#(#data_types),*) {
+                match self {
+                    #pattern => #ret_value,
+                    val @ _ => #failed_block,
+                }
             }
-            Fields::Unit => (quote! {}, quote! { () }, quote! { () }),
         };
 
-        let other_arms = state.variant_states.iter().map(|variant| {
-            variant.variant.unwrap()
-        }).filter(|variant| {
-            &variant.ident != variant_ident
-        }).map(|variant| {
-            let data_pattern = match variant.fields {
-                Fields::Named(_) => quote! { {..} },
-                Fields::Unnamed(_) => quote! { (..) },
-                Fields::Unit => quote! {},
-            };
-            let variant_ident = &variant.ident;
-            quote! { #enum_name :: #variant_ident #data_pattern =>
-                      panic!(concat!("called `", stringify!(#enum_name), "::", stringify!(#fn_name),
-                                     "()` on a `", stringify!(#variant_ident), "` value"))
+        let ref_func = quote! {
+            #[inline]
+            #[track_caller]
+            #[doc = #doc_ref]
+            #[doc = #doc_else]
+            pub fn #ref_fn_name(&self) -> (#(&#data_types),*) {
+                match self {
+                    #pattern => #ret_value,
+                    val @ _ => #failed_block_ref,
+                }
             }
-        });
+        };
 
-        let variant_name = stringify!(variant_ident);
-        let func = quote! {
+        let mut_func = quote! {
+            #[inline]
             #[track_caller]
-            #[doc = "Unwraps this value to the `"]
-            #[doc = #variant_name]
-            #[doc = "` variant\n\nPanics if this value is of any other type"]
-            pub fn #fn_name(self) -> #ret_type {
+            #[doc = #doc_mut]
+            #[doc = #doc_else]
+            pub fn #mut_fn_name(&mut self) -> (#(&mut #data_types),*) {
                 match self {
-                    #enum_name ::#variant_ident #data_pattern => #ret_value,
-                    #(#other_arms),*
+                    #pattern => #ret_value,
+                    val @ _ => #failed_block_mut,
                 }
             }
         };
-        funcs.push(func);
+
+        if info.owned && state.default_info.owned {
+            funcs.push(func);
+        }
+        if info.ref_ && state.default_info.ref_ {
+            funcs.push(ref_func);
+        }
+        if info.ref_mut && state.default_info.ref_mut {
+            funcs.push(mut_func);
+        }
     }
 
     let imp = quote! {
@@ -95,3 +127,44 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStre
 
     Ok(imp)
 }
+
+fn get_field_info(fields: &Fields) -> (TokenStream, TokenStream, Vec<&Type>) {
+    match fields {
+        Fields::Named(_) => panic!("cannot unwrap anonymous records"),
+        Fields::Unnamed(ref fields) => {
+            let (idents, types) = fields
+                .unnamed
+                .iter()
+                .enumerate()
+                .map(|(n, it)| (format_ident!("field_{n}"), &it.ty))
+                .unzip::<_, _, Vec<_>, Vec<_>>();
+            (quote! { (#(#idents),*) }, quote! { (#(#idents),*) }, types)
+        }
+        Fields::Unit => (quote! {}, quote! { () }, vec![]),
+    }
+}
+
+fn failed_block(state: &State, enum_name: &Ident, fn_name: &Ident) -> TokenStream {
+    let arms = state
+        .variant_states
+        .iter()
+        .map(|it| it.variant.unwrap())
+        .map(|variant| {
+            let data_pattern = match variant.fields {
+                Fields::Named(_) => quote! { {..} },
+                Fields::Unnamed(_) => quote! { (..) },
+                Fields::Unit => quote! {},
+            };
+            let variant_ident = &variant.ident;
+            let panic_msg = format!(
+                "called `{enum_name}::{fn_name}()` on a `{enum_name}::{variant_ident}` value"
+            );
+            quote! { #enum_name :: #variant_ident #data_pattern => panic!(#panic_msg) }
+        });
+
+    quote! {
+        match val {
+            #(#arms),*
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 84fb944..7e10f6b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,6 +27,8 @@
 //! [`Constructor`]: crate::Constructor
 //! [`IsVariant`]: crate::IsVariant
 //! [`Unwrap`]: crate::Unwrap
+//! [`TryUnwrap`]: crate::TryUnwrap
+
 // The README includes doctests requiring these features. To make sure that
 // tests pass when not all features are provided we exclude it when the
 // required features are not available.
@@ -52,6 +54,11 @@ mod convert;
 #[cfg(feature = "try_into")]
 pub use self::convert::TryIntoError;
 
+#[cfg(feature = "try_unwrap")]
+mod try_unwrap;
+#[cfg(feature = "try_unwrap")]
+pub use self::try_unwrap::TryUnwrapError;
+
 #[cfg(feature = "debug")]
 pub mod fmt;
 
@@ -88,7 +95,8 @@ pub use self::r#str::FromStrError;
     feature = "not",
     feature = "sum",
     feature = "try_into",
-    feature = "unwrap"
+    feature = "unwrap",
+    feature = "try_unwrap",
 )))]
 compile_error!(
     "at least one derive feature must be enabled (or the \"full\" one enabling all the derives)"
diff --git a/src/try_unwrap.rs b/src/try_unwrap.rs
new file mode 100644
index 0000000..7761025
--- /dev/null
+++ b/src/try_unwrap.rs
@@ -0,0 +1,48 @@
+/// Error returned by the derived [`TryUnwrap`] implementation.
+///
+/// [`TryUnwrap`]: macro@crate::TryUnwrap
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct TryUnwrapError<T> {
+    /// Original input value which failed to convert via the derived
+    /// [`TryUnwrap`] implementation.
+    ///
+    /// [`TryUnwrap`]: macro@crate::TryUnwrap
+    pub input: T,
+    enum_name: &'static str,
+    variant_name: &'static str,
+    func_name: &'static str,
+}
+
+impl<T> TryUnwrapError<T> {
+    #[doc(hidden)]
+    #[must_use]
+    #[inline]
+    pub const fn new(
+        input: T,
+        enum_name: &'static str,
+        variant_name: &'static str,
+        func_name: &'static str,
+    ) -> Self {
+        Self {
+            input,
+            enum_name,
+            variant_name,
+            func_name,
+        }
+    }
+}
+
+impl<T> core::fmt::Display for TryUnwrapError<T> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(
+            f,
+            "Attempt to call `{enum_name}::{func_name}()` on a `{enum_name}::{variant_name}` value",
+            enum_name = self.enum_name,
+            variant_name = self.variant_name,
+            func_name = self.func_name,
+        )
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T: core::fmt::Debug> std::error::Error for TryUnwrapError<T> {}


Test patch:
diff --git a/tests/try_unwrap.rs b/tests/try_unwrap.rs
new file mode 100644
index 0000000..2280bc8
--- /dev/null
+++ b/tests/try_unwrap.rs
@@ -0,0 +1,128 @@
+#![allow(dead_code)]
+
+use derive_more::TryUnwrap;
+
+#[derive(TryUnwrap)]
+enum Either<TLeft, TRight> {
+    Left(TLeft),
+    Right(TRight),
+}
+
+#[derive(TryUnwrap)]
+#[derive(Debug, PartialEq)]
+#[try_unwrap(ref, ref_mut)]
+enum Maybe<T> {
+    Nothing,
+    Just(T),
+}
+
+#[derive(TryUnwrap)]
+enum Color {
+    RGB(u8, u8, u8),
+    CMYK(u8, u8, u8, u8),
+}
+
+/// With lifetime
+#[derive(TryUnwrap)]
+enum Nonsense<'a, T> {
+    Ref(&'a T),
+    NoRef,
+    #[try_unwrap(ignore)]
+    NoRefIgnored,
+}
+
+#[derive(TryUnwrap)]
+enum WithConstraints<T>
+where
+    T: Copy,
+{
+    One(T),
+    Two,
+}
+
+#[derive(TryUnwrap)]
+enum KitchenSink<'a, 'b, T1: Copy, T2: Clone>
+where
+    T2: Into<T1> + 'b,
+{
+    Left(&'a T1),
+    Right(&'b T2),
+    OwnBoth(T1, T2),
+    Empty,
+    NeverMind(),
+    NothingToSeeHere(),
+}
+
+/// Single variant enum
+#[derive(TryUnwrap)]
+enum Single {
+    Value(i32),
+}
+
+#[derive(TryUnwrap)]
+#[derive(Debug, PartialEq)]
+#[try_unwrap(ref, ref_mut)]
+enum Tuple<T> {
+    None,
+    Single(T),
+    Double(T, T),
+    Triple(T, T, T),
+}
+
+#[test]
+pub fn test_try_unwrap() {
+    assert_eq!(Maybe::<()>::Nothing.try_unwrap_nothing().ok(), Some(()));
+    assert_eq!((&Maybe::Just(1)).try_unwrap_just_ref().ok(), Some(&1));
+    assert_eq!(
+        (&mut Maybe::Just(42)).try_unwrap_just_mut().ok(),
+        Some(&mut 42)
+    );
+
+    assert_eq!(
+        Maybe::<()>::Nothing.try_unwrap_just().map_err(|e| e.input),
+        Err(Maybe::<()>::Nothing),
+    );
+    assert_eq!(
+        (&Maybe::Just(1))
+            .try_unwrap_nothing_ref()
+            .map_err(|e| e.input),
+        Err(&Maybe::Just(1)),
+    );
+    assert_eq!(
+        (&mut Maybe::Just(42))
+            .try_unwrap_nothing_mut()
+            .map_err(|e| e.to_string()),
+        Err(
+            "Attempt to call `Maybe::try_unwrap_nothing_mut()` on a `Maybe::Just` value"
+                .to_string()
+        ),
+    );
+}
+
+#[test]
+pub fn test_try_unwrap_mut_1() {
+    let mut value = Tuple::Double(1, 12);
+
+    if let Ok((a, b)) = value.try_unwrap_double_mut() {
+        *a = 9;
+        *b = 10;
+    }
+
+    assert_eq!(value, Tuple::Double(9, 10));
+}
+
+#[test]
+pub fn test_try_unwrap_mut_2() {
+    let mut value = Tuple::Single(128);
+
+    if let Ok(x) = value.try_unwrap_single_mut() {
+        *x *= 2;
+    }
+
+    if let Err(e) = value.try_unwrap_none_mut() {
+        let x = *e.input.try_unwrap_single_ref().unwrap_or(&0);
+        *e.input = Tuple::Double(x - 1, x);
+    }
+
+    assert_eq!(value, Tuple::Double(255, 256));
+}
diff --git a/tests/unwrap.rs b/tests/unwrap.rs
index d5ef516..a690d28 100644
--- a/tests/unwrap.rs
+++ b/tests/unwrap.rs
@@ -9,6 +9,8 @@ enum Either<TLeft, TRight> {
 }
 
 #[derive(Unwrap)]
+#[derive(Debug)]
+#[unwrap(ref, ref_mut)]
 enum Maybe<T> {
     Nothing,
     Just(T),
@@ -20,6 +22,7 @@ enum Color {
     CMYK(u8, u8, u8, u8),
 }
 
+/// With lifetime
 #[derive(Unwrap)]
 enum Nonsense<'a, T> {
     Ref(&'a T),
@@ -36,6 +39,7 @@ where
     One(T),
     Two,
 }
+
 #[derive(Unwrap)]
 enum KitchenSink<'a, 'b, T1: Copy, T2: Clone>
 where
@@ -49,13 +53,66 @@ where
     NothingToSeeHere(),
 }
 
+/// Single variant enum
+#[derive(Unwrap)]
+enum Single {
+    Value(i32),
+}
+
+#[derive(Unwrap)]
+#[derive(Debug, PartialEq)]
+#[unwrap(ref, ref_mut)]
+enum Tuple<T> {
+    None,
+    Single(T),
+    Double(T, T),
+    Triple(T, T, T),
+}
+
 #[test]
 pub fn test_unwrap() {
     assert_eq!(Maybe::<()>::Nothing.unwrap_nothing(), ());
+    assert_eq!(Maybe::Just(1).unwrap_just(), 1);
+
+    assert_eq!((&Maybe::Just(42)).unwrap_just_ref(), &42);
+    assert_eq!((&mut Maybe::Just(42)).unwrap_just_mut(), &mut 42);
+}
+
+#[test]
+#[should_panic]
+pub fn test_unwrap_panic_1() {
+    Maybe::<()>::Nothing.unwrap_just();
+}
+
+#[test]
+#[should_panic]
+pub fn test_unwrap_panic_2() {
+    Maybe::Just(2).unwrap_nothing();
 }
 
 #[test]
 #[should_panic]
-pub fn test_unwrap_panic() {
-    Maybe::<()>::Nothing.unwrap_just()
+pub fn test_unwrap_ref_panic() {
+    Maybe::Just(2).unwrap_nothing_ref();
+}
+
+#[test]
+pub fn test_unwrap_mut_1() {
+    let mut value = Tuple::Double(1, 12);
+
+    let (a, b) = value.unwrap_double_mut();
+    *a = 9;
+    *b = 10;
+
+    assert_eq!(value, Tuple::Double(9, 10))
+}
+
+#[test]
+pub fn test_unwrap_mut_2() {
+    let mut value = Tuple::Single(128);
+
+    let x = value.unwrap_single_mut();
+    *x *= 2;
+
+    assert_eq!(value, Tuple::Single(256));
 }