- 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.
The first hurdle with Rust and strings comes from misaligned expectations. A string literal (
"Hi!") isn’t an instance of a
String in Rust. You don’t need to fully understand the code below yet, just know that it outputs the types of the values sent to
$ cargo run Type is: &str Type is: alloc::string::String
Wait, there’s more.
string primitives and
String instances. It automatically does what you want, when you want it, without incurring the overhead of creating an
Object for every
string. When you call a method on a primitive
Rust has similar magic, it just doesn’t always do it for you.
There is a lot written about Strings. Don’t miss the official docs and other great posts out there.
- Strings in the Rust docs
- Why Are There Two Types of Strings In Rust?
- How do I convert a &str to a String in Rust?
- Rust String vs str slices
- Rust: str vs String
- String vs &str in Rust
Rust strings in a nutshell
String literals are borrowed string slices. That is to say: they are pointers to a substring in other string data. The Rust compiler puts all of our literal strings in a bucket somewhere and replaces the values with pointers. This lets Rust optimize away duplicate strings and is why you have a pointer to a string slice, vs a pointer to a single
You can verify the optimizations are real, if you don’t believe me. Copy-paste the print line below a gazillion times (or less) and see that it only has a minor impact on the executable size.
You can also run the (not-rust-specific)
strings command to output all the string data in a binary.
$ strings target/release/200-prints | grep TESTING TESTING:12345678901234567890123456789012345678901234567890
If you run that command on the
200-unique-prints binary in the node-to-rust repo, you’ll get much more output.
Strings are the strings that you know and love. You can change them, cut them up, shrink them, expand them, all sorts of great stuff. All that brings along additional cost though. Maybe you don’t care, maybe you do. It’s in your hands now.
How do you make a
In short: use the
.to_owned() method on a
&str (a “borrowed” string slice) to turn it into an “owned”
For what its worth, this method calls the code below under the hood.
This is why we had to go over ownership before we got into strings. String literals start off borrowed. If you need an owned
String, you have to convert it (copy it, essentially).
You’re telling me I need to write
Yes. And no. Sort of. For now, accept “yes” until we get into Traits and generics.
All these options also turn a
&str into a
String. If this is your first foray into Rust from node.js, don’t worry about this section. This is for developers who have read all the other opinions out there and are wondering why other methods aren’t the “one true way.”
traitis sharable behavior. We haven’t gotten to them yet, but think of a trait like a
something.to_string() converts something into a string. It’s commonly implemeted as part of the
Display trait. You’ll see a lot of posts that recommend
.to_string() and a lot that don’t.
The nuances in the recommendation stem from how much you want the compiler to help you. As your applications grow — especially when you start to deal with generics — you’ll inevitably refactor some types into other types. A value that was initially a
&str might end up being refactored into something else. If the new value still implements
Display, then it has a
.to_string() method. The compiler won’t complain.t
.to_owned() turns something borrowed into something owned, often by cloning. Turning a borrowed
not-string into an owned
not-string gives the compiler the context necessary to raise an error. If you’re OK with the difference, it’s easy to change a
.to_owned() into a
.to_string(). If you weren’t expecting it, then you highlighted an issue before it became a problem.
If you use
.to_string(), the world won’t explode. If you are telling someone they shouldn’t use
.to_string(), you have to be able to explain why. Just like you would if you used the word octopodes.
Clippy has a lint that will alert you if you use
something.into() will (attempt) to turn something into a destination type by calling
String::from(something). If the destination type is a
String and your something is a
&str then you’ll get the behavior you’re looking for. The concerns are similar to those above. Are you really trying to turn something into something else, or are you trying to turn a
&str into a
String? If it’s the former, then
.into() works fine, if it’s the latter then there are better ways to do it.
String::from(something) is more specific than
.into(). You are explicitly stating your destination type, but it has the same issues as
.to_string(). All it expresses is that you want a string but you don’t care from where.
format!() is for formatting. This is the only one you should definitely not use for simply creating a
Clippy also has a lint for this one: clippy::useless_format
The path to this “one, true answer” is mapped out here. At the end of the road, everything points to
String is the first half of the string issue. The second is which to use in function arguments when you want to create an easy-to-use API that takes either string literals (