From 5cb7dbd38f91528197f016c43e990dc62fa2536d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tu=E1=BA=A5n-Anh=20Nguy=E1=BB=85n?= Date: Sun, 6 Oct 2019 16:12:51 +0700 Subject: [PATCH] Explain how to embed lifetime-constrained types in user-ptr Fixes #21. --- guide/src/custom-types.md | 75 +++++++++++++++++++++++++++++++++++ guide/src/type-conversions.md | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/guide/src/custom-types.md b/guide/src/custom-types.md index d246572..7b32c35 100644 --- a/guide/src/custom-types.md +++ b/guide/src/custom-types.md @@ -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); + +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 { + tree: Rc, + node: super::PhantomNode<'tree, T> + } + } +} + +type RentingNode = inner::RentingNode<()>; + +#[defun(user_ptr)] +fn root_node(tree: Value) -> Result { + let rc: &Rc = tree.into_rust()?; + Ok(RentingNode::new(rc.clone(), |tree| tree.root_node())) +} + +#[defun(user_ptr)] +fn child(node: &RentingNode) -> Result { + node.map(|n| n.child()) +} +``` + +Note that there's no `unsafe` involved directly, as the soundness proofs are already encapsulated in `rental` macros. diff --git a/guide/src/type-conversions.md b/guide/src/type-conversions.md index 6844708..58f5984 100644 --- a/guide/src/type-conversions.md +++ b/guide/src/type-conversions.md @@ -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`.