Convenient hacks for Rust in Production #1

convenient strategies for writing Rust

Trick 1: impl Into<T>

Often APIs can quite cumbersome in Rust, especially for cases where you would often want to provide simple overloading in any other programming language.

In Rust you can achieve the similar results by using impl Into<T> pattern. For example if we wanna accept both string and char as a function argument we can build a middleware type to handle overloads using sumtypes.

#[derive(Debug)] enum StringOrChar { Str(&'static str), Char(char), } impl Into<StringOrChar> for &'static str { fn into(v: &'static str) -> StringOrChar { StringOrChar::Str(v) } } impl Into<StringOrChar> for char { fn into(v: char) -> StringOrChar { StringOrChar::Char(v) } } fn foo(sc: impl Into<StringOrChar>) { println!("{:#?}", sc.into()); } fn main() { foo("with string"); foo('w'); foo('i'); foo('t'); foo('h'); }

Trick 2: Cow<'static, str> or Arc<str>

As we know there are issues with dealing with allocations in Rust when dealing with strings, especially when generalizing it over all the available String types in Rust. We especially are faced with issues regarding dual allocations for String buffer types and avoiding allocations for string slices.

Most of these issues are addressed by Cow<'static, str> or Arc<str>, but these aren’t quite perfect, and often we don’t quite need the Cow-ness of the Cow type, but also find the Arc<str>’s of ability to work with owned String types lacking.

fn accept_str_and_string(s: impl Into<Cow<'static, str>>) { println!("{}", s.into()); } pub fn main() { accept_str_and_string("static string"); // works! accept_str_and_string("static string".to_string()); // works! let src = "wow!"; accept_str_and_string(src); // works! } // what doesn't work with Cow<'static, str> fn foo(s: impl AsRef<str>) { accept_str_and_string(s.as_ref()) // doesn't work }

In such cases we can work with some community alternatives.

Arcstr

It’s essentially a simple reference counted string type, which can be used much the same way as Arc<str> and provide cheap copies, but also provide zero cost way of dealing with static str.

cervine

Of course not all problems can be solved without Cow-ness, in those cases we have cervine. It provides somewhat greater flexibility than plain Cow type and can be used to create types that also provide cheap copies.

Eg: cervine::Cow<'static, Rc<str>, str>

use arcstr::ArcStr; fn accept_str_and_string(s: impl Into<ArcStr>) { println!("{}", s.into()); } fn main() { accept_str_and_string("static string"); // works! accept_str_and_string("static string".to_string()); // works! let src = "wow!"; accept_str_and_string(src); // works! foo(src); } fn foo(s: impl AsRef<str>) { accept_str_and_string(s.as_ref()) // now this works too! }

Trick 3: Always Benchmark

Updated(2024).

We now have a library called divan. Which simplifies building benchmarks and handling insights significantly. So I won’t dive deeper into how to build manually benchmarking interfaces, I recommend to use the above library without any worries.

// divan usage use divan::{black_box, counter::BytesCount, AllocProfiler, Bencher}; use image::{GenericImage, ImageBuffer, Rgba}; fn main() { divan::main(); } #[global_allocator] // hooks into allocator and provides insights into allocations static ALLOC: AllocProfiler = AllocProfiler::system(); fn make_image(pixel: Rgba<u8>) -> ImageBuffer<Rgba<u8>, Vec<u8>> { ImageBuffer::from_pixel(2048, 2048, pixel) } // https://github.com/image-rs/image/blob/v0.24.6/benches/copy_from.rs #[divan::bench(max_time = 1)] fn copy_from(bencher: Bencher) { let src = make_image(Rgba([255u8, 0, 0, 255])); let mut dst = make_image(Rgba([0u8, 0, 0, 255])); bencher .counter(BytesCount::of_slice(&*src)) .bench_local(|| black_box(&mut dst).copy_from(black_box(&src), 0, 0)); } /// Baseline for `copy_from`. #[divan::bench(max_time = 1)] fn memcpy(bencher: Bencher) { let src = make_image(Rgba([255u8, 0, 0, 255])); let mut dst = vec![0; src.len()]; bencher .counter(BytesCount::of_slice(&*src)) .bench_local(|| black_box(&mut dst).copy_from_slice(black_box(&src))); }