.as_deref() not tranforming Option<&String> to Option<&str>, please help

Hello, I am somewhat new to the language. I came across some behavior that is a little bit confusing. I thought .as_deref() on an Option<&String> would return an Option<&str> through Deref coercion, but it is isn't. I tried looking at the documentation for both the .as_deref() method and the impl Deref for String part, and I can't figure out why it isn't returning an Option<&str>

#![allow(unused)]
fn main() {
    let p = String::from("hey");
    let x = Some(&p);
    assert_eq!(x.as_deref(), Some("hey"));
    
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:5:30
  |
5 |     assert_eq!(x.as_deref(), Some("hey"));
  |                              ^^^^^^^^^^^ expected `Option<&String>`, found `Option<&str>`
  |
  = note: expected enum `Option<&String>`
             found enum `Option<&str>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error

I thought maybe this could be because the underlying .deref() could work differently on a &String than it does a String, but this shows otherwise:

    let test = String::from("hello");
    let op = Some(&test);
    println!("{}", type_name_of_val(&op));
    let wow = match op {
        Some(t) => Some(t.deref()),
        None => None,
    };

    println!("{}", type_name_of_val(&wow));

output:

:!cargo run
[No write since last change]
   Compiling chapter v0.1.0 (/home/duc/Documents/Projects/learning_rust/chapter-15/chapter)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/chapter`
core::option::Option<&alloc::string::String>
core::option::Option<&str>
2 Likes

It's because references implement Deref to themselves. The .as_deref() method lets you go from Option<String> to Option<&str>, but it doesn't help you for Option<&String>.

as_deref takes a &Option<T> to an Option<&<T as Deref>::Target>.

You have a T = &String, and <&String as Deref>::Target is String. So this converts a &Option<&String> to an Option<&String>.

(If you had an Option<String>, it would convert &Option<String> to Option<&str>, because <String as Deref>::Target is str.)

You can instead use:

x.map(|s| &**s)

Here, s is a &String, *s is a String, **s is a str, and &**s is a &str.

6 Likes

An alternative would be:

x.map(String::as_str)

Which I like more because it is readable and says precise what is happening.

5 Likes

I prefer x.map(|s| s.as_str()), or x.map(String::as_str) [1]

fun fact:

if you use x.map(<String as Deref>::deref) (or the shorthand x.map(String::deref), or even x.map(Deref::deref)), it actually works too.

if you search functions with the type signature &String->&str in the standard library, you get 4 functions with the exact signature, among which only String::as_str() is an inherent (and const) method on String, the others are all trait methods.


  1. it's slightly longer, but when reading code, double * operators always makes me stop and think longer than I wanted :wink: â†Šī¸Ž

The as_deref of an Option<T> is doing something like: &(my_type_T.deref())>, provided T implements the Deref trait.

For example T=u32 would fail to run:

    let p = 5u32;
    let x = &Some(p);
    x.as_deref()

since u32 does not implement Deref.

When T=&String it does something akin to&(my_string_ref.deref()). The part within brackets is String, the result is &String.

With T=String that yields &str.


I wouldn't know how to do it best but you do have some suggestions, I just wanted to give my interpretation of what happens.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.