How to use `AsRef<str>` (not RefAs<str>)

I've read that AsRef can be used in a function's type signature to allow passing in String or &str nicely as both can "auto-deref" to str.

I'm trying to use AsRef in the type signature of a trait and hit this compilation error when using String in the implementation:

 fn my_trait_function(&self) -> String {
   |                            ^^^^^^ expected trait std::convert::AsRef, found struct `std::string::String`
   |
   = note: expected type `fn ... -> (dyn std::convert::AsRef<str> + 'static)`
              found type `.fn ... -> std::string::String`

I tried to reassure myself that AsRef<str> works with a minimal example:

use std::fmt::Debug;

fn foo(s: dyn AsRef<str> + Debug) -> dyn AsRef<str> + Debug {
    "boo"
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(foo(), "boo");
    }
}

but so far I'm even more confused by what the compiler complains about. I'm sure I'm missing something important... Any ideas?

1 Like

yes, AsRef<...> is useful for function arguments, so that either a str or a String can be passed without conversion at the call site (usually you'd use it with parameterization: fn func<T: AsRef<str>>(s: T), not with dyn)

but it's not clear to me what you are trying to do by returning a dyn AsRef<...>, instead of a concrete String ?

1 Like

Ohh... I see what I'm doing wrong now...
AsRef is the trait, I'm asking for a trait object (dyn AsRef) when I should be asking for a type that implements the trait (T: AsRef).

fn foo<T: AsRef<str>>(s: T) -> &str { ... }

Still a little confused though.

The clue was that it was asking for dyn, this isn't needed when specifying trait bounds (I think?)

Thanks - I think I was wanting to return either a &str or a String... which I'm now realising is deliberately not allowed? Or maybe just a bad idea in general.

This was with others' implementing the trait in mind, so that they could write an implementation for &str or String as they saw fit.

correct, dyn is for run-time dynamic dispatch, which is unnecessary here

Well, the call site needs to know the type, and whether it is an owned value or a reference, to know how to clean it up.

Not sure if this is applicable to your scenario, but for future reference - when you want to abstract over a piece of data that may be owned or borrowed (or may transition from one to the other), std::borrow::Cow is your friend :cow:

2 Likes

What a coincidence, I just finished another minimal example to help get my head around this:

use std::fmt::{Debug, Display};

fn cowsay<T: AsRef<str> + Display>(s: T) -> String {
    format!("cow: {}", s)
}

trait Cowsay<T: AsRef<str> + Display> {
    fn cowsay(s: T) -> String;
}

struct Moo {}

impl Cowsay<String> for Moo {
    fn cowsay(s: String) -> String {
        cowsay(s)
    }
}

impl Cowsay<&str> for Moo {
    fn cowsay(s: &str) -> String {
        cowsay(s)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(cowsay("foo"), "cow: foo");
        assert_eq!(Moo::cowsay("foo"), "cow: foo");
        assert_eq!(Moo::cowsay(String::from("foo")), "cow: foo");
    }
}

Thanks for reminding me about Cow, gonna make a mental note to start trying to use that.

This was with others' implementing the trait in mind, so that they could write an implementation for &str or String as they saw fit.

Cow is a good suggestion in the sense that it wraps either a borrowed reference or owned object, but it's meant as an optimization to avoid unnecessary copies, for example in parsers

you need to explicitly manage them, though, which can result in needing more boilerplate code

so i'm not sure it's what you're looking for here as a convenience feature for trait implementers

edit: which makes me wonder how to use Cow with AsRef, the following passthrough function doesn't compilie (cannot return value referencing function parameter input):

fn func<'a, T: AsRef<str> + 'a>(input: T) -> Cow<'a, str> {
    return Cow::Borrowed(input.as_ref());
}

Does this help answer why?

&str compiles but String fails to .

i.e. this is fine... which is unimpressive

fn func2<'a>(input: &'a str) -> Cow<'a, str> {
    return Cow::Borrowed(input.as_ref());
}

the pass-through is an absurd example in the first place :smile: , in practice it'd at least check the input string (say, "is it uppercase") and conditionally either return a reference to that, or return a changed owned string
but i can't get it to work with AsRef and lifetimes

You should take a reference as input, not an owned value.

fn func<T: AsRef<str>>(input: &T) -> Cow<'_, str> {
    return Cow::Borrowed(input.as_ref());
}
2 Likes

oh that was so obvious in retrospect, thanks !

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.