I was checking the phone number
crate and I came across with the function signature
pub fn is_viable<S: AsRef<str>>(string: S) -> bool
Here what is the point of AsRef
how is it different from using &str
?
I was checking the phone number
crate and I came across with the function signature
pub fn is_viable<S: AsRef<str>>(string: S) -> bool
Here what is the point of AsRef
how is it different from using &str
?
It's a slight convenience for the caller of the function. You can pass either a normal &str
, or anything else that meets the AsRef<str>
trait bound. Most notably, String
meets that bound, so this allows you to call the same function on either a &str
or a String
.
(If the signature was just pub fun is_viable(string: &str) -> bool
, and all you had to call that function on was a String
, you would need to call .as_ref()
on your string first in order to make it a &str
you could pass to is_viable
. So it's nicer if is_viable
does that for you.)
that sounds cool. and as a writer how do you handle the param string of type S? can I use it as a drop in replacement for the place I use &str?
The only thing you know about S
here is that it is a type that implements AsRef<str>
. (Because of the S: AsRef<str>
in the type signature.)
So the one and only thing you're allowed to do with a parameter of type S
in this case it is to use methods from the AsRef
trait. AsRef
has exactly one method, .as_ref()
. So the only thing the implementer of is_viable
can do is use .as_ref()
on that parameter, getting (in this case) a value of type &str
.
So if you're implementing a function, and you intend for your function to take a &str
argument, you might instead have it take a generic argument S: AsRef<str>
, and then call .as_ref()
on that argument to get the &str
you needed. As the implementer of the function, either way you end up with a supplied &str
, but the generic version makes it slightly more ergonomic for callers who have not a &str
but, say, a String
. (Because you're the one converting it for them, and they don't have to think about the conversion at all.)
I see thank you
Using AsRef<str>
most often than not will just allow the caller to avoid having to type a leading &
in front of their expression, since most stuff that is AsRef<str>
is also Deref<Target = str>
(e.g., coercing a &String
to a &str
).
This, then has a few minor drawbacks to be aware of:
The function can take ownership of an input String
when what it really needed was just a borrow; this may lead a beginner into .clone
-ing needlessly.
For instance,
fn print_str<S> (s: S)
where
S : AsRef<str>,
{
let s: &str = s.as_ref();
println!("{}", s);
}
fn main ()
{
let s = String::from("Hello, World!");
print_str(s);
print_str(s);
}
errors with
error[E0382]: use of moved value: `s`
--> src/main.rs:11:15
|
9 | let s = String::from("Hello, World!");
| - move occurs because `s` has type `std::string::String`, which does not implement the `Copy` trait
10 | print_str(s);
| - value moved here
11 | print_str(s);
| ^ value used here after move
and a beginner may be tempted to fix this with:
fn print_str<S> (s: S)
where
S : AsRef<str>,
{
let s: &str = s.as_ref();
println!("{}", s);
}
fn main ()
{
let s = String::from("Hello, World!");
print_str(s.clone());
print_str(s);
}
Given how a generic function works (a generic function is actually a function factory; for every different type <S>
the generic is called with, Rust will monomorphise the generic into a new concrete function (print_str::<String>
, print_str::<&str>, print_str::<Rc<str>>
), i.e., compile a new version of the function), this leads to a little bit longer compile times and potentially bigger binaries. This can be fixed with the following pattern, (done, for instance, by ::momo
's #[momo]
attribute macro):
fn foo<S> (s: S)
where
S : AsRef<str>,
{
let s: &str = s.as_ref();
/*
* VERY
* LONG
* FUNCTION
* '
* S
* BODY
*/
}
becomes
#[inline]
fn foo<S> (s: S) // same public prototype
where
S : AsRef<str>,
{
fn foo_common (s: &str)
{
/*
* VERY
* LONG
* FUNCTION
* '
* S
* BODY
*/
}
foo_common(s.as_ref())
}
This being said, the trait is indeed useful: imagine that you want to take a sequence of strings (to, let's say, print them all):
How do you express the "sequence-ness"?
Vec<..>
is too restrictive (in the same way as taking a String
instead of a &str
is), so you can take a impl AsRef<[..]>
, in the same vein as you were taking a impl AsRef<str>
. As I said, there is little benefit in this case, where taking a &[..]
is just as fine.
How do you express the "string-ness"?
String
is too restrictive (e.g., it wouldn't accept &["Hello", "World!"]
), but it turns out that so is &str
! Indeed, taking a &[&str]
does not accept a
&[String::from("Hello"), String::from("World!")]
.
This is where things like AsRef
really do shine: by taking a &[impl AsRef<str>]
, we manage to have a function accepting both cases!
fn print_strs (strs: &[impl AsRef<str>])
{
println!("{}", ::itertools::Itertools::format(
strs.iter().map(AsRef::as_ref),
", ",
));
}
fn main ()
{
print_strs(&[
"Hello",
"World!",
]);
print_strs(&[
"Hello".to_string(),
"World!".to_string(),
]);
}
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.