yutannihilation / savvy

A simple R extension interface using Rust
https://yutannihilation.github.io/savvy/guide/
MIT License
63 stars 3 forks source link

Example of wrapping external crates #281

Closed hughjonesd closed 1 month ago

hughjonesd commented 1 month ago

This is maybe more of a documentation request. I'd like to provide an R interface to an external crate, which has its own structs and functions. At the moment my approach is to create wrapper structs and functions, then invoke the #[savvy] macro. But is there a better way to do this which doesn't require a lot of boilerplate code?

yutannihilation commented 1 month ago

Hi, thanks for the content suggestion of savvy's documentation! Could you elaborate? I can't imagine in what situation you need "a lot of boilerplate code".

hughjonesd commented 1 month ago

Suppose just that you want to return a (reference to a) struct from an external crate, and/or pass that back into the crate's functions from R.

yutannihilation commented 1 month ago

How is it different from this simple example? I think you can just define a newtype and pass it around.

https://yutannihilation.github.io/savvy/guide/struct.html

hughjonesd commented 1 month ago

That works for a struct defined in my own crate. Can I do the same for a struct defined in an external crate?

hughjonesd commented 1 month ago

Ah, do you mean something like

[savvy]

type Foo = External::Bar

yutannihilation commented 1 month ago

No, I meant this. Do you know the newtype pattern? It's a common practice to deal with the restriction of the orphan rule.

#[savvy]
struct Foo(External::Bar)
hughjonesd commented 1 month ago

Thank you for this. I get the impression that

#[savvy]
struct Foo<'a>(External::Bar<'a>);

does not currently work, or is that just me?

yutannihilation commented 1 month ago

Ah, you are right. Savvy currently doesn't support a struct with lifetime and I don't think it will because crossing the boundary of FFI means losing the track of the lifetimes. I think an obvious workaround is to use 'static lifetime if you can.

yutannihilation commented 1 month ago

Added a short description about lifetime. Thanks for the suggestion.

https://yutannihilation.github.io/savvy/guide/struct.html#lifetime

Honestly, I'm yet to figure out what's the supposed way to deal with lifetimes, but if you provide more details, I might able to help.

hughjonesd commented 1 month ago

I don't think I understand lifetimes (or rust in general) enough myself to be very helpful... I'm trying to wrap a bunch of the objects in ratatui, and some of them have lifetimes.

yutannihilation commented 1 month ago

Hmm, sorry, your description is too vague and I'm not sure if I can help you. I wish you can eventually find some good example to illustrate what your problem is!

hughjonesd commented 1 month ago

Here is a specific example: ratatui::widgets::Paragraph has a lifetime <'a>, and an associated method pub fn block(self, block: Block<'a>) -> Self. I'd like to wrap this type and method. But if I wrap it in e.g. a struct, that will require a lifetime too. I tried extendr and wasn't able to get past the borrow checker - they are also thinking of supporting lifetimes, but don't yet. I guess savvy would be the same.

Would something like Box help here?

See https://docs.rs/ratatui/latest/ratatui/widgets/struct.Paragraph.html

yutannihilation commented 1 month ago

In that case, Paragraph's lifetime comes from this 'a.

where
    T: Into<Text<'a>>,

Text implements From<&'a str>.

https://docs.rs/ratatui/latest/ratatui/text/struct.Text.html#impl-From%3C%26str%3E-for-Text%3C'a%3E

So, you can pass &str with 'static lifetime. You might find String::leak() useful for this.

https://doc.rust-lang.org/std/string/struct.String.html#method.leak