-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ocaml-interop integration #48
Conversation
@tizoc I'm back to looking at this, it seems pretty good right now, so I hope to release this branch as long as you don't see any issues with it. |
@zshipko ok, I will take a look at it soon. Right now I am playing with an experimental fast global-roots implementation, if it works well it could replace the need for local roots which would help us simplify the API quite a bit. |
@@ -53,7 +53,7 @@ fn variant_attrs(attrs: &[syn::Attribute]) -> Attrs { | |||
}) | |||
} | |||
|
|||
pub fn tovalue_derive(mut s: synstructure::Structure) -> proc_macro::TokenStream { | |||
pub fn intovalue_derive(mut s: synstructure::Structure) -> proc_macro::TokenStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zshipko question, why did you go from to
-> into
? (I'm curious about this because I went the opposite direction in ocaml-interop, by making into_rust
-> to_rust
because I think that in rust the into
assumes that self
is consumed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah this is something I probably want to change back. In this case the trait is actually consuming self
, but I don't think that is really the best idea here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the thing is that Value
is Copy
right? I think I had it like that for the same reason, but since the original value is still available I decided to switch to the to_
prefix. I am not really sure about what the convention is in cases like this, but my reasoning is that (in addition to what I said on the previous sentence) since Value
is more like a reference than the actual value to_
made more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I remember that IntoValue
consumes self
to make Custom
values better integrated. It seems like consuming self
is actually kind of nice because it prevents the value from being accessed after being converted to OCaml.
I will need to think about the various trade-offs a little more...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the thing is that
Value
isCopy
right? I think I had it like that for the same reason, but since the original value is still available I decided to switch to theto_
prefix. I am not really sure about what the convention is in cases like this, but my reasoning is that (in addition to what I said on the previous sentence) sinceValue
is more like a reference than the actual valueto_
made more sense.
The main impetus behind the name change was that clippy was complaining about it being named To*
and not taking a reference. But I think your explanation makes more sense.
unsafe impl ToValue for $t { | ||
fn to_value(self) -> $crate::Value { | ||
$crate::Value::int(self as crate::Int) | ||
unsafe impl IntoValue for $t { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One mismatch between ocaml-interop and ocaml-rs is:
FromOCaml<OCamlT> for RustT
vsFromValue for RustT
ToOCaml<OCamlT> for RustT
vsToValue for RustT
The idea for the API I went with in ocaml-interop is that one value could support multiple conversions, but maybe it is not necessary to do it this way and it would be simpler to just use intermediary types. This is something I am still not decided on, not sure if you have any thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having the option to support multiple conversions is nice, but I left this in because I don't see the harm in providing an option (and the 1-to-1 type conversion can be simpler in some cases). But since ocaml-interop
values can convert to and from ocaml-rs
values it should be possible to use OCaml<T>
arguments and return types too.
@@ -252,7 +228,7 @@ unsafe impl ToValue for &str { | |||
} | |||
} | |||
|
|||
unsafe impl FromValue for &mut str { | |||
unsafe impl<'a> FromValue for &mut str { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this <'a>
needed here?
src/macros.rs
Outdated
macro_rules! body { | ||
($(($($param:expr),*))? $code:block) => { | ||
$crate::sys::caml_body!($(($($param.0),*))? $code); | ||
$crate::interop::ocaml_frame!($gc, ($($param),*), { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that ocaml_frame!
only makes sense when there is at least one root, when there are no roots there is no need to open a frame. So this macro should use +
instead of *
.
src/value.rs
Outdated
let mut frame = rt.open_frame(); | ||
let gc = frame.initialize(&[]); | ||
let mut x = unsafe { crate::interop::internal::OCamlRawRoot::reserve(gc) }; | ||
unsafe { core::mem::transmute(x.keep(OCaml::<T>::from_value(value))) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 this doesn't look very safe. I don't think it makes sense to convert a Value
into an OCamlRef
because Value
doesn't have any information about the runtime handle like OCaml<'a, T>
does. So producing an OCamlRef
from a Value
is not really safe. The other thing is that the frame you create here gets dropped as soon as this method returns, so it is not really doing much.
I would just remove this one.
src/value.rs
Outdated
fn from_value(value: Value) -> OCaml<'a, T> { | ||
let rt = unsafe { &mut Runtime::recover_handle() }; | ||
let x: OCaml<T> = unsafe { OCaml::new(rt, value.0) }; | ||
unsafe { core::mem::transmute(x) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as the OCamlRef
conversion. I think that by default you can assume that for any case where you are using Runtime::recover_handle()
, the conversion you are making is not safe.
I don't have anything else to say for now, maybe I should give it another pass tomorrow because I may have missed something. But I think it will be during a possibly eventual adaptation of Tezedge code to use |
Thanks! I appreciate the review and I will take a look at some of those rough edges - I forgot about that Since you have an actual use-case, I am happy to help turn At that point, it seems like it could be best to let |
Well, my intention has always been to eventually merge everything under the The reason I haven't suggested that yet is that I still consider I'm still undecided on some things, like complicating the traits to support multi-way conversion, and also I'm waiting to see how things like fast-global-roots go to see if I can simplify the API further (by getting rid of the need to use frames and local roots). Also, for now the focus has been only on the Rust->OCaml side because that is what has been needed the most in Tezedge until now, but there are things that could be done to make things better for OCaml->Rust calls (both Tezos having Rust dependencies now + Tezedge also going to require that soon are real use-cases I have for that now). |
Alright cool, that makes sense! I'm mostly just pointing out that the whole It does sound like releasing this PR, once I clean things up, would be a good intermediate step. But in the long run we are kind of waiting on the Also, the current |
Alright @tizoc, I have removed those conversions between If you don't see any more issues, I will make a new release for these changes in the next few days! |
c5216a9
to
f508c34
Compare
derive/src/derive.rs
Outdated
}) | ||
} else if attrs.unboxed { | ||
if variant.bindings().len() > 1 { | ||
panic!("ocaml cannot unboxed record with multiple fields") | ||
} | ||
variant.each(|field| quote!(#field.to_value())) | ||
variant.each(|field| quote!(unsafe {#field.into_value(gc) })) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zshipko is this call really unsafe
? the fn into_value(...)
declarations that show up on this diff don't have the unsafe
qualifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that doesn't need to be in an unsafe block
src/value.rs
Outdated
return None; | ||
} | ||
/// Convert from `Value` into `interop::OCaml<T>` | ||
pub fn interop<'a, T>(&self, rt: &'a Runtime) -> OCaml<'a, T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one probably needs to be marked unsafe
, because as it is it allows you to convert into any T
, which is not always valid.
See tizoc/ocaml-interop#13