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.
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:
Arc<str>, and any others you may come across!
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!
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!