ubolonton / emacs-module-rs

Rust binding and tools for Emacs's dynamic modules
336 stars 22 forks source link

`FromLisp` for `Vec`? #47

Open fosskers opened 2 years ago

fosskers commented 2 years ago

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 commented 2 years ago

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,
        }
    }
}
justinbarclay commented 5 months ago

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 commented 5 months ago

@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

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();