Skip to content

Herman J. Radtke III

The _with Function Pattern in Rust

I really like _with style functions that accept a FnOnce callback. The scoping rules work out really well when using these functions. I was working with the slab crate recently and used the Slab#insert_with function. This function takes a callback where an object is supposed to be allocated before being inserted into the slab. The function returns an Option type. I was trying to figure out to drop the newly created object if the function returned None (meaning the insertion failed). After a few minutes it dawned on me that the object was out already dropped!

Example:

extern crate slab;

#[derive(Debug)]
struct MyType {
    index: usize,
    value: String
}

type Slab = ::slab::Slab<MyType, usize>;

fn main() {

    let mut slab: Slab = Slab::new(128);

    let f = |index: usize| -> MyType {
        MyType {
            index: index,
            value: "a very very very long string".to_string()
        }
    };

    match slab.insert_with(f) {
        Some(index) => {
            println!("Inserted MyType at index {}", index);
        },
        None => {
            // If insertion fails, `MyType` will go out of scope and be dropped/freed.
            println!("Failed to insert into slab");
        }
    }
}

The newly allocated MyType is moved from the callback into the Slab#insert_with scope. If the insert fails, then Slab#insert_with returns None. The newly allocated type is left within the Slab::insert_with function scope. Once Slab#insert_with returns, the newly allocated type will be automatically dropped. When an object is dropped, the destructor is called and any allocated memory will be freed.

[edit: An explanation of drop semantics can be found here.]

The slab crate is an elegant little library that allocates a chunk of memory on the heap and stores values using a custom type for the index. It incorporates a lot of the core Rust concepts. I am learning a lot by studying the code.