- Day 1: From nvm to rustup
- Day 2: From npm to cargo
- Day 3: Setting up VS Code
- Day 4: Hello World (and your first two WTFs)
- Day 5: Borrowing & Ownership
- Day 6: Strings, part 1
- Day 7: Syntax and Language, part 1
- Day 8: Language Part 2: From objects and classes to HashMaps and structs
- Day 9: Language Part 3: Class Methods for Rust Structs (+ enums!)
- Day 10: From Mixins to Traits
- Day 11: The Module System
- → Day 12: Strings, Part 2
- Day 13: Results & Options
- Day 14: Managing Errors
- Day 15: Closures
- Day 16: Lifetimes, references, and
- Day 17: Arrays, Loops, and Iterators
- Day 18: Async
- Day 19: Starting a large project
- Day 20: CLI Arguments & Logging
- Day 21: Building and Running WebAssembly
- Day 22: Using JSON
- Day 23: Cheating The Borrow Checker
- Day 24: Crates & Tools
The code in this series can be found at vinodotdev/node-to-rust
This guide is not a comprehensive Rust tutorial. This guide tries to balance technical accuracy with readability and errs on the side of “gets the point across” vs being 100% correct. If you have a correction or think something needs deeper clarification, send a note on Twitter at @jsoverson, @vinodotdev, or join our Discord channel.
Now that you’re getting confident, you are probably itching to start your own projects. You know how to write some basic Rust. You know how to structure code. You’re done with contrived examples with traffic lights. As you play around and start writing your API, you’ll inevitably write some function that needs an owned
String. That’s easy. We’ve done that before. Then it hits you…
Your API, dotted with functions like below:
forces your users to write code like this
It’s so ugly. I just couldn’t believe it at first. All I wanted was to create a satisfying API that could take a
String as well as string literals. I didn’t want to make my users do things like add
.to_owned() all over the place. It’s madness. It’s hideous.
I spent a long time tracking down what to do. This is my story.
Should I use
String for my function arguments?
The first question you need to ask is: “Do I need to own or borrow the passed value?”
For those of you still wrapping your head around owning and borrowing, think of this question as: “Do I need my own version of this value or do I just need to look at its data and move on?”
When you’re borrowing the value
If you don’t need your own version, then accept a reference. The main question then becomes “Should I use
&String” and the answer is almost always
&str. Why? Well look at the function below.
You can pass a string literal directly, a
&str assigned to a variable, and you can pass a reference to a
&String which works automagically.
Why? Because the trait
Borrow<str> is implemented for
String. Anywhere you need a borrowed
str, you can use a borrowed
String without any hassle. The reverse is not true. If you change the function signature to accept a
&String and try to pass a
&str, you’ll get a compile error.
When you need an owned value
If you need an owned value then you need a
String but there are a couple things to consider.
It is cumbersome to manually convert loads of
String. This is a genuine problem when you want to expose a satisfying, easy to use API. The example in the introduction is contrived but it is exactly what you’ll deal with frequently.
You should let the users of your API decide how to create owned values for you. In other words, you shouldn’t simply accept
&strs everywhere and convert them yourself. The user may have better ways of getting you the data.
Here’s where you want to think about your API. Are you looking to own a string that comes from wherever? Or do you expect your functions to be called with a mix of string literals and/or
If it’s the former, then you’re not really looking for a
&str or a
String. You’re looking for something that has a
.to_string() method. That is, you’re looking for something that implements
ToString. Types that implement
ToString are common, you get it for free by implementing
If it’s the latter, then you’re looking for the common ground between a
&str, which we found clues to in the section above.
For functions that accept potentially generated strings
For a function that accepts arbitrary strings from anywhere — generated or non — you can accept arguments that implement
We talked about
impl [trait] vs
dyn [trait] yesterday but now I through in new syntax above. This is Rust’s generic syntax. The function
needs_string above could have been written like this:
Nothing would need to change in the code. What’s the difference? Very little.
impl [trait] in argument position is less powerful than generic syntax. Rust also has a
where keyword which you can use to make the same thing yet another way:
There’s zero difference between the
where syntax and the
<T: ToString> syntax. Adding the separate
where clause is for readability.
For functions that expect a mix of string literals and
If you didn’t read this section, you wouldn’t lose much. Using the
ToString method covers a lot of cases. You do however open your users up to the same concerns we described with using
.to_string() to convert
String in Day 6: Strings, Part 1. Many structs implement
Display. A user could pass a lot of objects to the function above without the compiler complaining. Additionally, implementations of
.to_string() may not always be cheap. Your users don’t necessarily know the internals of your code. You might be doing a lot of extra work for no good reason.
Since we already learned that a
&String can take the place of a
&str, we can generalize our inputs to anything that can be borrowed like a
You could use anything that implements
Borrow<str> and the below would work. However, you should accept anything that implements
AsRef<str> instead. What’s the difference? Borrow assumes more and can fail,
AsRef<str> assumes little and explicitly must not fail. There are more differences but they don’t matter for our usage here.
The below is extremely similar to the above but notice that our
ip_address is no longer a valid argument. Just because it has a printable string form doesn’t mean it can be trivially taken as a