Skip to content

Commit

Permalink
Explain how to embed lifetime-constrained types in user-ptr
Browse files Browse the repository at this point in the history
Fixes #21.
  • Loading branch information
ubolonton committed Oct 6, 2019
1 parent 86c8aad commit 5cb7dbd
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
75 changes: 75 additions & 0 deletions guide/src/custom-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,78 @@ fn init(env: &Env) -> Result<()> {
Ok(map.get(&key).into_lisp(v.env)?)
}
```

## Lifetime-constrained Types

When a type is constrained by a (non-static) lifetime, its value cannot be embedded unchanged. Before embedding, the lifetime must be **soundly** elided. In other words, static ownership must be correctly given up.

The typical example is a struct holding a reference to another struct:

```rust
pub struct Tree;

pub struct Node<'t> {
pub tree: &'t Tree,
}

impl Tree {
pub fn root_node(&self) -> Node<'_> {
...
}
}

impl<'t> Node<'t> {
pub fn child(&self) -> Node<'t> {
...
}
}
```

In this case, the lifetime can be elided by turning the static reference into a dynamic ref-counted pointer. The [rental crate](https://github.com/jpernst/rental) provides a convenient way to do this:

```rust
#[macro_use]
extern crate rental;

use std::{rc::Rc, marker::PhantomData};
use emacs::{defun, Result};

// PhantomData is need because map_suffix requires a type parameter.
// See https://github.com/jpernst/rental/issues/35.
pub struct PhantomNode<'t, T>(Node<'t>, PhantomData<T>);

impl<'t> PhantomNode<'t, ()> {
fn child(&self) -> Self {
PhantomNode(self.0.child(), PhantomData)
}
}

rental! {
pub mod inner {
use std::rc::Rc;

// Self-referential struct that holds both
// the actual Node and the ref-counted Tree.
#[rental(map_suffix = "T")]
pub struct RentingNode<T: 'static> {
tree: Rc<super::Tree>,
node: super::PhantomNode<'tree, T>
}
}
}

type RentingNode = inner::RentingNode<()>;

#[defun(user_ptr)]
fn root_node(tree: Value) -> Result<RentingNode> {
let rc: &Rc<Tree> = tree.into_rust()?;
Ok(RentingNode::new(rc.clone(), |tree| tree.root_node()))
}

#[defun(user_ptr)]
fn child(node: &RentingNode) -> Result<RentingNode> {
node.map(|n| n.child())
}
```

Note that there's no `unsafe` involved directly, as the soundness proofs are already encapsulated in `rental` macros.
2 changes: 1 addition & 1 deletion guide/src/type-conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ true.into_lisp(env)?; // t
false.into_lisp(env)?; // nil
```

## Integer conversion
## Integer Conversion

Integer conversion is lossless by default, which means that a module will signal an "out of range" `rust-error` in cases such as:
- A `#[defun]` expecting `u8` gets passed `-1`.
Expand Down

1 comment on commit 5cb7dbd

@justinbarclay
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much improved! Thanks for writing this :)

Please sign in to comment.