List

List is a different beast. It's pretty complex. You might think of it as a HashMap, but it's different in that:

  • List elements can be either named or unnamed individually (e.g., list(a = 1, 2, c = 3)).
  • List names can be duplicated (e.g., list(a = 1, a = 2)).

To make things simple, savvy treats a list as a pair of the same length of

  • a character vector containing names, using "" (empty string) to represent missingness (actually, this is the convention of R itself)
  • a collection of arbitrary SEXP elements

Since list is a very convenient data structure in R, you can come up with a lot of convenient interfaces for list. However, savvy intentionally provides only very limited interfaces. In my opinion, Rust should touch list data as little as possible because it's too complex.

Read values from a list

names_iter()

names_iter() returns an iterator of &str.

/// @export
#[savvy]
fn print_list_names(x: ListSexp) -> savvy::Result<()> {
    for k in x.names_iter() {
        if k.is_empty() {
            r_println!("(no name)");
        } else {
            r_println!(k);
        }
        r_println!("");
    }

    Ok(())
}
print_list_names(list(a = 1, 2, c = 3))
#> a
#> (no name)
#> c

values_iter()

values_iter() returns an iterator of Sexp enum. You can convert Sexp to TypedSexp by .into_typed() and then use match to extract the inner data.

/// @export
#[savvy]
fn print_list_values_if_int(x: ListSexp) -> savvy::Result<()>  {
    for v in x.values_iter() {
        match v.into_typed() {
            TypedSexp::Integer(i) => r_println!("int {}\n", i.as_slice()[0]),
            _ => r_println("not int")
        }
    }

    Ok(())
}
print_list_values_if_int(list(a = 1, b = 1L, c = "1"))
#> not int
#> int 1
#> not int

iter()

If you want pairs of name and value, you can use iter(). This is basically a std::iter::Zip of the two iterators explained above.

/// @export
#[savvy]
fn print_list(x: ListSexp)  -> savvy::Result<()> {
    for (k, v) in x.iter() {
        // ...snip...
    }

    Ok(())
}

Put values to a list

new()

OwnedListSexp's new() is different than other types; the second argument (named) indicates whether the list is named or unnamed. If false, the list doesn't have name and all operations on name like set_name() are simply ignored.

set_name()

set_name() simply sets a name at the specified position.

/// @export
#[savvy]
fn list_with_no_values() -> savvy::Result<savvy::Sexp> {
    let mut out = OwnedListSexp::new(2, true)?;

    out.set_name(0, "foo")?;
    out.set_name(1, "bar")?;

    out.into()
}
list_with_no_values()
#> $foo
#> NULL
#> 
#> $bar
#> NULL
#> 

set_value()

set_value() sets a value at the specified position. "Value" is an arbitrary type that implmenents Into<Sexp> trait. Since all {type}Sexp types implements it, you can simply pass it like below.

/// @export
#[savvy]
fn list_with_no_names() -> savvy::Result<savvy::Sexp> {
    let mut out = OwnedListSexp::new(2, false)?;

    let mut e1 = OwnedIntegerSexp::new(1)?;
    e1[0] = 100;
    
    let mut e2 = OwnedStringSexp::new(1)?;
    e2.set_elt(0, "cool")?;

    out.set_value(0, e1)?;
    out.set_value(1, e2)?;

    out.into()
}
list_with_no_names()
#> [[1]]
#> [1] 100
#> 
#> [[2]]
#> [1] "cool"
#> 

set_name_and_value()

set_name_and_value() is simply set_name() + set_value(). Probably this is what you need in most of the cases.

/// @export
#[savvy]
fn list_with_both() -> savvy::Result<savvy::Sexp> {
    let mut out = OwnedListSexp::new(2, true)?;

    let mut e1 = OwnedIntegerSexp::new(1)?;
    e1[0] = 100;
    
    let mut e2 = OwnedStringSexp::new(1)?;
    e2.set_elt(0, "cool")?;

    out.set_name_and_value(0, "foo", e1)?;
    out.set_name_and_value(1, "bar", e2)?;

    out.into()
}
list_with_both()
#> $foo
#> [1] 100
#> 
#> $bar
#> [1] "cool"
#>