emacs-module-rs icon indicating copy to clipboard operation
emacs-module-rs copied to clipboard

`FromLisp` for `Vec`?

Open fosskers opened this issue 3 years ago • 3 comments

Hi there, this is a really cool library and I'm excited to speed up my Emacs with Rust.

I'm having trouble figuring out how to send Elisp lists to my Rust functions. Something like this doesn't seem to compile:

#[emacs::defun]
fn mean(items: Vec<f64>) -> emacs::Result<f64> { ... }

as it complains that Vec is missing a FromLisp instance. I see that Elisp Vectors are supported, but lists aren't the same. Am I missing something really basic?

Please and thanks.

fosskers avatar Jun 20 '22 18:06 fosskers

In the meantime, I'm making due with:

/// An iterator over what is assumed to be an underlying Elisp List.
struct List<'a> {
    items: Value<'a>,
}

impl<'a> Iterator for List<'a> {
    type Item = Value<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        match (self.items.car(), self.items.cdr()) {
            (Ok(v), Ok(next)) if self.items.is_not_nil() => {
                self.items = next;
                Some(v)
            }
            _ => None,
        }
    }
}

fosskers avatar Jun 20 '22 19:06 fosskers

I was working on this too recently, and I like your iterator option, but sometimes I just want to use into() to get a vec, so I took everything a step further

// An iterator over what is assumed to be an underlying Elisp List.
struct List<'a>(Value<'a>);

// This is a custom implementation of the FromLisp trait for the List type
// So that we can _error_ out safely when going from a Value to a List
impl<'a> FromLisp<'a> for List<'a> {
  fn from_lisp(value: Value<'a>) -> Result<Self> {
    let env = value.env;
    if env.call("listp", [value]).unwrap().is_not_nil() {
      Ok(List(value))
    } else {
      env.signal(env.intern("wrong-type-argument")?, [value, env.intern("list")?])
    }
  }
}

impl<'a> Iterator for List<'a> {
    type Item = Value<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        match (self.0.car(), self.0.cdr()) {
            (Ok(v), Ok(next)) if self.0.is_not_nil() => {
                self.0 = next;
                Some(v)
            }
            _ => None,
        }
    }
}

// We could call this directly on the Value type or our wrapper for the value type
// but then we would regularly expect panics here when a non list item is passed in
impl<'e, T: FromLisp<'e>> Into<Vec<T>> for List<'e>{
  fn into(self) -> Vec<T> {
    let mut result: Vec<T> = Vec::new();
    let mut list = self.0;

    while list.is_not_nil() {
      let car = list.car().unwrap();
      result.push(T::from_lisp(car).unwrap());
      list = list.cdr().unwrap();
    }
    result
  }
}

justinbarclay avatar Mar 30 '24 21:03 justinbarclay

@ubolonton I am not sure if you're open to expanding the interface, but I would love to see a system like above to allow converting from a Value to a Vec<T>

If we could inline the above, then converting to a Vec<String> is comes out to:

        options.string_delimiters = List::from_lisp(new_value)?.into();

justinbarclay avatar Mar 30 '24 21:03 justinbarclay