They're Stringish... ish
This article isn't anything special, but I want to show you something cool I found when writing a library!
I won't take all that long - we'll write a small function and pimp it out over time.
Strings
Rust has several "string-ish" types. This situation is great for the ecosystem, as developers always have a choice of allocation or borrowing.
Unfortunately, we find a problem if we take any string type into a function. Let's say we search a list of cities for some input. We'll barely do anything new - we can call list_of_cities.contains()
. It does all the hard work for us! (Well, not really...)
So, we'll probably want to take a search key and a list. Then, we can return either true or false so callers know if we found their city!
This solution technically works, but our old pal Clippy reminds us that our function can become a one-liner. (seriously, add Clippy to your IDE!)
Anyways, let's apply its suggestions:
I think that looks good! Let's write a test to make sure it works.
Well, it works when we pass in an &str
, but Strings are off-limits. The compiler appropriately suggests a fix on the caller side of things.
help: consider borrowing here
|
65 | let y = city_search;
That said, what if we could fix the problem inside our function?
Do you think we need to? In the current example, the whole String situation looks pretty dumb. However, what if we set aside our favorite city? Maybe the city keeps changing? For many general functions, callers could do any number of things!
In these situations, many high-level libraries tend to use AsRef
over their favorite string type. We'll discuss that more later. For now, let's look at how we can use it in our program.
// ...psst. the changes are here! ποΈ ποΈ ποΈ ποΈ
As you can see, our new and improved function now takes an impl AsRef<str>
. On the compiler side of things, this means that your function is now technically generic. Though... maybe it's a "simple generic."
If you're a nerd, you can write it like this:
The Rust compiler is just as pleased to accept this goofy function!
To wrap our groundbreaking function up, let's write some tests to show that we can use various string types. These include: &str
, String
, Box<str>
, Cow<'_, str>
, Box<str>
, Rc<str>
/Arc<str>
, and any others you may come across!
Being Friendly
Even though taking an AsRef
can make it easier and cleaner to call your functions, it's crucial to consider what your AsRef
is doing. In general, it won't allocate any memory. However, if you're working in a low-level library or codebase, ask yourself:
- Could this do something unexpected?
- Am I telling callers what's going on here?
- Is it worth it? Am I meant to be here? Why was I born?
These questions are appropriate across most decisions you make when programming. Still, they help out a lot when using idioms like AsRef
. Make sure to give it a little thought first!
Wrapping Up
For most, using AsRef
on string, path, or other types can help users focus on their code. With generics, your functions become reusable across many unique types and situations. Unsurprisingly, "syntactic sugar" tokens like AsRef
are in the same boat.
Use them to your advantage. It's the little things that matter most!