1

I am trying to figure out how to apply Rust lifetimes to add some compile-time enforcement to Erlang NIF modules. NIF modules are shared libraries normally written in C that provide extensions.

A simplified prototype of the callback you would write in C looks like this:

Handle my_nif_function(Heap *heap, Handle handle);

You are provided a handle and a pointer to the heap that owns it. In your callback you may inspect the input handle, create more handles on the heap, and return one of them as the function return. The heap and all its handles become invalid after your callback returns, so you must not store copies of the heap or its handles during the callback. Unfortunately I’ve seen people do exactly this and it eventually results in a mysterious emulator crash. Can Rust enforce these lifetime constraints?

I think the heap can be easily managed by turning it into a reference.

fn my_nif_function(heap: &Heap, handle: Handle) -> Handle

But how can I link the lifetime of the input and output handles to the heap?

Another wrinkle to this is that you can also create your own heaps and handles which are allowed to live outside the scope of a callback invocation. In C++ I would use std::unique_ptr with a custom destructor. What is the Rust equivalent? The [simplified] C API for managing heaps looks like this:

Heap *create_heap();
void destroy_heap(Heap *);

Reference: NIFs are described here: http://www.erlang.org/doc/man/erl_nif.html . The Erlang names for "heaps" and "handles" are "environments" and "terms". I used the names "heaps" and "handles" so that the question would be more broadly understood.

goertzenator
  • 1,960
  • 18
  • 28
  • Have you already been introduced to the [`ContravariantLifetime`](http://doc.rust-lang.org/std/marker/struct.ContravariantLifetime.html) marker? – Shepmaster Jan 27 '15 at 16:04
  • That looks interesting, but I have no idea how to apply it. My compulsion is to use heap's lifetime and attach it to handle, but in all the examples I've seen you can only attach lifetimes to references (and handle is not a reference.) – goertzenator Jan 27 '15 at 16:45
  • I've got some leads. At least part of the story is here: https://botbot.me/mozilla/rust/2015-01-18/?page=1 – goertzenator Jan 27 '15 at 17:07

1 Answers1

3

Rust 1.0

The various marker types have been unified into one: PhantomData

use std::ptr;
use std::marker::PhantomData;

struct Heap {
    ptr: *const u8,
}

impl Heap {
    fn new(c_ptr: *const u8) -> Heap {
        Heap {
            ptr: c_ptr
        }
    }

    fn wrap_handle<'a>(&'a self, c_handle: *const u8) -> Handle<'a> {
        Handle {
            ptr: c_handle,
            marker: PhantomData,
        }
    }
}

struct Handle<'a> {
    ptr: *const u8,
    marker: PhantomData<&'a ()>, 
}

fn main() {
    let longer_heap = Heap::new(ptr::null());

    let handle = {
        let shorter_heap = Heap::new(ptr::null());

        let longer_handle = longer_heap.wrap_handle(ptr::null());
        let shorter_handle = shorter_heap.wrap_handle(ptr::null());

        // longer_handle // ok to return
        // shorter_handle // error: `shorter_heap` does not live long enough
    };
}

Original Answer

Here's an example of using ContravariantLifetime. We wrap the raw heap pointer into a struct and then wrap raw handle pointers in another struct, reusing the lifetime of the heap.

use std::ptr;
use std::marker::ContravariantLifetime;

struct Heap {
    ptr: *const u8,
}

impl Heap {
    fn new(c_ptr: *const u8) -> Heap {
        Heap {
            ptr: c_ptr
        }
    }

    fn wrap_handle<'a>(&'a self, c_handle: *const u8) -> Handle<'a> {
        Handle {
            ptr: c_handle,
            marker: ContravariantLifetime,
        }
    }
}

struct Handle<'a> {
    ptr: *const u8,
    marker: ContravariantLifetime<'a>,
}

fn main() {
    let longer_heap = Heap::new(ptr::null());

    let handle = {
        let shorter_heap = Heap::new(ptr::null());

        let longer_handle = longer_heap.wrap_handle(ptr::null());
        let shorter_handle = shorter_heap.wrap_handle(ptr::null());

        // longer_handle // ok to return
        // shorter_handle // error: `shorter_heap` does not live long enough
    };
}

Lifetime markers

There are 3 lifetime markers. I won't attempt to replicate the reasonably good but dense documentation here, but can also point out the dense Wikipedia page, which might be some small assistance. I've listed them in the order that you are most likely to use them:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Fantastic! The one thing that still looks weird to me is the creation of Handle in wrap_handle. Why does the marker value have to be specified and what does it mean? Is there really a choice of putting anything other than "marker: ContravariantLifetime"? – goertzenator Jan 27 '15 at 17:59
  • You have to put a value because that's the way Rust works - all fields need to be specified at construction to ensure that the struct is always in a valid state. In this example, you can only put a `ContravariantLifetime` (because that's what the field type is), but there are other options. I'll add a small bit about those to the answer. – Shepmaster Jan 27 '15 at 18:02