Making Rust Strings is silly
This commit is contained in:
parent
c7b11e107d
commit
98a118f6c3
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: The fastest way to make Rust Strings
|
||||||
|
tags:
|
||||||
|
- rust
|
||||||
|
- software
|
||||||
|
- software development
|
||||||
|
---
|
||||||
|
|
||||||
|
A friend of mine learning how to code with Python was complaining about the
|
||||||
|
myth that "there's a Pythonic way" to do things. The "one true way" concept
|
||||||
|
wasn't ever taken seriously in Python, not even by the standard library.
|
||||||
|
Practically speaking, it's impossible _not_ to have multiple ways to accomplish
|
||||||
|
the same outcome in a robust programming language's standard library. This
|
||||||
|
_flexibility_ jumped out at me while hacking on some Rust code lately: how many
|
||||||
|
ways can you turn `str`
|
||||||
|
into `String`?
|
||||||
|
|
||||||
|
In Rust `"this thing"` is a [primitive `str`
|
||||||
|
type](https://doc.rust-lang.org/std/primitive.str.html#) and will have the
|
||||||
|
`&'static` lifetime. Without diving into lifetimes and how Rust ownership
|
||||||
|
works, this is basically read-only memory that exists for the duration of the
|
||||||
|
program. They're _static_ and you can't do much with it. In _most_ APIs you'll
|
||||||
|
need the [`String`
|
||||||
|
type](https://doc.rust-lang.org/std/string/struct.String.html), which will give
|
||||||
|
you an allocated bit of data you can play around with.
|
||||||
|
|
||||||
|
Without much effort I came up with five different ways that I have written Rust
|
||||||
|
code to perform this conversion:
|
||||||
|
|
||||||
|
1. `String::from("The boring way")`
|
||||||
|
2. `"Using a trait".into()`
|
||||||
|
3. `"This is actually a trait too".to_string()`
|
||||||
|
4. `"Lol, this is also a trait".to_owned()`
|
||||||
|
5. `format!("Wake up and choose violence")`
|
||||||
|
|
||||||
|
---
|
||||||
|
If you have some other nifty ways to create `String`s, let me know on
|
||||||
|
[Twitter](https://twitter.com) or via email (`rtyler@` this domain)!
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
But which is the most fastest?! I wrote the following very important, and very serious microbenchmarking code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use microbench::{self, Options};
|
||||||
|
|
||||||
|
fn into_trait() {
|
||||||
|
let _s: String = "Rust is cool!".into();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_string() {
|
||||||
|
let _s: String = "Rust is cool!".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format() {
|
||||||
|
let _s: String = format!("Rust is cool!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn owned() {
|
||||||
|
let _s: String = "Rust is cool!".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_from() {
|
||||||
|
let _s: String = String::from("Rust is cool!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = Options::default();
|
||||||
|
microbench::bench(&options, "String::from!", || string_from());
|
||||||
|
microbench::bench(&options, "Into<String>", || into_trait());
|
||||||
|
microbench::bench(&options, "ToString<str>", || to_string());
|
||||||
|
microbench::bench(&options, "ToOwned<str>", || owned());
|
||||||
|
microbench::bench(&options, "format!", || format());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I compiled the program with `rustc` version 1.63.0 and after running some truly
|
||||||
|
rigorous and scientific tests on my workstation, I am thrilled to share the results:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo run
|
||||||
|
Compiling rust-strings-are-silly v0.1.0 (/home/tyler/source/github/rtyler/rust-strings-are-silly)
|
||||||
|
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
|
||||||
|
Running `target/debug/rust-strings-are-silly`
|
||||||
|
String::from! (5.0s) ... 278.552 ns/iter (0.991 R²)
|
||||||
|
Into<String> (5.0s) ... 286.293 ns/iter (0.983 R²)
|
||||||
|
ToString<str> (5.0s) ... 292.736 ns/iter (0.987 R²)
|
||||||
|
ToOwned<str> (5.0s) ... 290.276 ns/iter (0.985 R²)
|
||||||
|
format! (5.0s) ... 300.144 ns/iter (0.995 R²)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**HOW INTERESTING!**
|
||||||
|
|
||||||
|
Well, not really.
|
||||||
|
|
||||||
|
Microbenchmarking like this has **lots** of flaws,
|
||||||
|
especially when sampling on a single machine running many other concurrent
|
||||||
|
processes. After executing the tool a few times, one common pattern that I did see was that
|
||||||
|
the `format!` macro is consistently the slowest way to create `String`s. In
|
||||||
|
fact `cargo clippy` will complain about you using in this way, not because it's
|
||||||
|
slow, but because it's a "useless use of `format!`", which I can agree with! :)
|
||||||
|
|
||||||
|
|
||||||
|
Choosing between the rest of them probably is nothing more than a style choice
|
||||||
|
of the developers working on any given Rust project. With these types of things
|
||||||
|
it's typically best to adopt one consistent way of doing things _within the
|
||||||
|
codebase_ to improve readability, but they're all functionally equivalent..
|
||||||
|
|
||||||
|
In Rust there's no "one true way" to create a `String`, but my personal
|
||||||
|
preference is `.into()` for no other reason than it is the fewest
|
||||||
|
characters to type!
|
Loading…
Reference in New Issue