Blog post series: Rust patterns

Here's a few simple patterns that I sometimes run across:

Optional buffer allocation / Allocate and reference

Best example I can think of at the moment is where you may have an API that optionally allows the caller to provide their own buffer to reuse between calls, perhaps for a type that is constructed with a builder pattern. In the bellow snippet, a vector will be allocated and a reference to it returned in the event that no buffer is found. Otherwise, the provided buffer is simply obtained.

fn do_thing(buffer: Option<&mut [u8]>) {
    let mut allocated: Vec<u8>;
    let buffer: &mut [u8] = match buffer {
        Some(buffer) => buffer,
        None => {
            allocated = Vec::with_capacity(64 * 1024);
            &mut allocated
        }
    };
    
    do_thing_with_buffer(buffer);
}

The key is that you can assign a value to a previously-unassigned variable, and then take reference it so that the match arm suceeds in attaining a mutable reference with either arm.

Generic collection return type

Some APIs return Vec<T> instead of allowing the caller to to choose the type they want to collect into.

use std::iter::FromIterator;

// Where `X` is the type that the collection stores.
fn collect_thing<T: FromIterator<X>>(&self) -> T {
    self.iterable_thing().collect::<T>()
}

let thing_map = collect_thing::<HashMap>();
let thing_vec = collect_thing::<Vec>();

Although it's probably better to return impl Iterator<Item = X> nowadays.

Actions with pre and post conditions

It's often better to use a closure when you may perform tasks that have the same pre and post conditions. In the event that you'd also like the inner closure to reuse some variables that the function borrowed, you can pass the borrows to the closure to avoid an issue with the borrow checker.

fn backup(config: &Config, mut func: impl FnMut(&Config) -> io::Result<()>) -> io::Result<()> {
   let backup = do_backup_with(config)?;
   func()?;
   restore_backup(backup)
}

backup(config, || install_things_with(config));

Pass borrowed mutable borrows into inner closure

Sometimes you may run into a situation where you want to re-use a mutable borrow within the closure passed into a function that also borrows the same value.

fn do_thing(borrow: &mut Thing, mut func: impl FnMut(&mut Thing) {
    do_thing_with(borrow);
    func(borrow);
    do_other_thing();
}

do_thing(&mut value, |value| also_with(value));

Generic hash key

Some types don't work very well as keys for hash maps due to requiring an allocation and borrowing issues when getting and inserting keys. A solution is to hash the key in advance and use that.

fn hash_key<T: Hash>(key: &T) -> u64 {
    let mut hasher = DefaultHasher::new();
    key.hash(&mut hasher);
    hasher.finish()
}

let key = hash_key(&struct.name);
map.insert(key, struct);

Inherit methods of a base structure

In some situations it may be ideal to inherit the methods of a structure that you are extending. I've seen a need for this quite often when designing GTK widgets and views, where that widget should be capable of inheriting the methods of the base widget it extended. Implementing the Deref and AsRef traits on the type will do the job.


struct View { ... };

impl View {
    fn set_title(&self, title: &str);
    fn get_title(&self) -> &str;
}

struct SpecificView {
    view: View;
}

impl Deref for SpecificView {
    type Target = View;
    fn deref(&self) -> &Self::Target {
        &self.view
    }
}

impl AsRef<View> for SpecificView {
    fn as_ref(&self) -> &View {
        &self.view
    }
}

let specific = cascade!
    SpecificView::new();
    ..set_title("Specific View");
    | stack.add(specific.as_ref());
};

eprintln!("{}", specific.get_title());

Using Arc::strong_count to detect remaining threads

Sometimes you may want to know how many threads are left laying around instead of blocking to wait for them, which can be made more difficult when threads may spawn other threads to share a value. This can solve that:

let atomic_value = Arc::new(T);

let handles = spawn_many_threads_with(atomic_value.clone());

while Arc::strong_count(atomic_value) != 1 {
    // threads are still alive
    // possibly do other thing while we wait
    sleep(Duration::from_millis(1));
}

let result = handles.into_iter().map(|h| h.join().unwrap()).collect();
9 Likes