diff --git a/_posts/2022-10-28-rust-strings.md b/_posts/2022-10-28-rust-strings.md new file mode 100644 index 0000000..16cd18d --- /dev/null +++ b/_posts/2022-10-28-rust-strings.md @@ -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", || into_trait()); + microbench::bench(&options, "ToString", || to_string()); + microbench::bench(&options, "ToOwned", || 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 (5.0s) ... 286.293 ns/iter (0.983 R²) +ToString (5.0s) ... 292.736 ns/iter (0.987 R²) +ToOwned (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!