-
Notifications
You must be signed in to change notification settings - Fork 72
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mock-builder: add support for custom lifetimes #1335
Conversation
c2d8023
to
27039bc
Compare
26f9f80
to
37190f7
Compare
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 have to admit I can only follow on a much much higher level and on that, it looks good to me! I'd be interested in an argument for your safe unsafe
transmutation, see below.
libs/mock-builder/src/storage.rs
Outdated
let f = Box::new(f) as Box<dyn Fn(I) -> O>; | ||
let ptr = Box::into_raw(f); | ||
|
||
// SAFETY: transforming a wide pointer to an u128 is always safe. |
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.
Generally, I don't think this is true. Can you show a short proof?
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.
It's safe in the same way a "normal" reference is always transformable into a * const u64
. In fact, the following line does not require unsafe rust:
let reference: &u64 = ...
let ptr = reference as *const u64
This is safe because, it is just an address, a number. What it's not safe is what you do with that address, because all invariants and protections are broken after this.
This is why the other unsafe is the critical one, and you must ensure that the ptr still exists and corresponds to the same type as the original.
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.
Regarding "why exactly u128
?" It's because any &dyn Type
is a wide pointer: one part corresponds to the pointer itself and the other to the metadata pointer that contains the trait information of the closure. We do not care about how both pointers are sorted in memory, because we are undoing the same we did in the other method for the same type.
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.
It might be good to add more explanation in the comments--like how this enables us to use a fixed size in the CallInfo
struct for the ptr as opposed to having to know the size/alignment of the vtable ptr in the fat pointer.
Would it be reasonable to have CallInfo
have 2 usize fields for the closure address and vtable address instead and use ptr::from_raw_parts
to build ptr instead?
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.
Agree 馃憤馃徎 I'll add a better explanation. Regarding the 2 usize
fields, I think separating could confuse in the sense that seems that they should be used separately when not. We only need to allocate that, but not use the internals. Again, I think a good explanation into the code is needed.
libs/mock-builder/src/storage.rs
Outdated
)); | ||
} | ||
|
||
// SAFETY: The existance of this boxed clousure in consequent calls is ensured |
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.
nit
// SAFETY: The existance of this boxed clousure in consequent calls is ensured | |
// SAFETY: The existence of this boxed closure in consequent calls is ensured |
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 is some real inception level stuff: The existence is ensured by the forget
call 馃く
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.
Realize this means that you have done a very good review. Thanks so much! 馃檶馃徎
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 have to admit I can only follow on a much much higher level and on that, it looks good to me! I'd be interested in an argument for your safe unsafe
transmutation, see below.
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.
Looks good to me! I really like the better error formatting, known size for stored mock fns, and reduced transmute usage!
Added a few comments, but nothing blocking.
libs/mock-builder/src/storage.rs
Outdated
let f = Box::new(f) as Box<dyn Fn(I) -> O>; | ||
let ptr = Box::into_raw(f); | ||
|
||
// SAFETY: transforming a wide pointer to an u128 is always safe. |
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.
It might be good to add more explanation in the comments--like how this enables us to use a fixed size in the CallInfo
struct for the ptr as opposed to having to know the size/alignment of the vtable ptr in the fat pointer.
Would it be reasonable to have CallInfo
have 2 usize fields for the closure address and vtable address instead and use ptr::from_raw_parts
to build ptr instead?
libs/mock-builder/src/storage.rs
Outdated
)); | ||
} | ||
|
||
// SAFETY: The existence of this boxed closure in consequent calls is ensured |
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.
It might be good to update comment to indicate how these are being persisted--that forget takes ownership, but doesn't call destructor. I was initially unclear as to what was actually going on 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.
Ok! I'll do it. Thanks for the advice and for taking the time to deep into it, it's not an easy PR.
Error::TypeNotMatch => "The function is registered but the type mismatches", | ||
} | ||
) | ||
match self { |
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.
Nice!
|
||
/// Identify a call in the call storage | ||
pub type CallId = u64; | ||
|
||
trait Callable { | ||
fn as_any(&self) -> &dyn Any; | ||
struct CallInfo { |
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.
Very cool that we can have this as known fixed size, though have comment below in casting thread about the transmute.
@thea-leake, I've documented every weird case I was able to see. In the process, I've simplified a bit the |
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.
LGTM!
Description
This is the last technical requirement for
mock-builder
. After this PR, I think we can test any possible trait. 馃帀 Future work is to create procedural macros to reduce boilerplate. Roadmap hereNew features:
'static
) supported.Fixes #1322
Changes and Descriptions
'static