-
-
Notifications
You must be signed in to change notification settings - Fork 54
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Facet implementation for type with invariants #80
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
Comments
No, |
But.. yeah. I see what you mean. I don't think unsafe derives are a thing yet, really? |
Not yet rust-lang/rfcs#3715 |
I think this should be mentioned in the documentation for both the Maybe the derive trait can be renamed to |
What is the plan for implementing |
Maybe that's just the solution. It's true that building any type of the deployments facet is inherently unsafe unless you know what you're doing? I don't know. Maybe the solution here is to expose some method to verify invariants? It wouldn't solve the naming issue or the documentation issue, but we could make I quite like this idea. In fact, it could be specified as an attribute in the derived macro, something like:
Or something. It could even return an error value instead of just a bool so we have a nice user experience. |
Example: #[derive(Deserialize)]
#[serde(try_from = MaybeFive)]
struct NotFive {
int: i32,
}
#[derive(Deserialize)]
struct MaybeFive(i32);
impl TryFrom<MaybeFive> for NotFive {
type Err = FiveErr;
fn try_from(val: MaybeFive) -> Result<Self, Self::Err> {
if val.0 != 5 {
Ok(Self { int: val.0 })
} else {
Err(FiveErr)
}
}
struct FiveErr;
impl Display for FiveErr {...} |
Should Such as: struct MyByteVec {
// Safety invariant: `ptr` points to `len` bytes of memory
ptr: *mut u8,
len: usize,
} I think |
Yes, but not through the derive, if that makes sense? I really don't like the UX implications of renaming the derive macro to anything other than |
I'm starting to change my mind. |
I'm not sure I agree with the first example being the fault of the Are there other examples of |
That's actually a really good argument! |
It's also the logic Though a method for enforcing invariants during deserialization would be very nice. |
Any ideas on how to allow returning errors there without falling into "no context" or "&'static str only" or "woops we require an allocator now"? It would be nice to get the details of what invariant failed.... maybe &'static str is not that bad. |
I don't think I personally can think of anything except |
Maybe it would make sense to split |
I would much rather not because, facet is not really about serialization or deserialization. It just happens to be one of the many things you can do with it. I'm perfectly satisfied by the logic explained earlier. You can derive default and defeat your own invariance, and so deriving facet is exactly the same thing. We can either track validating invariants after parsing here, or we can open a new issue, but I think the rest has been covered pretty well. |
Would use of a Instead of fn validate(T) -> Result<(), &'static str>; You could do fn validate(T, error: &mut dyn ErrorSink) -> Option<T>;
trait ErrorSink {
fn report_error(error: ValidateionError);
}
impl ErrorSink for Option<ValidationError>;
#[cfg(feature = "alloc")]
impl ErrorSink for alloc::vec::Vec<VaidationError>;
#[non_exhaustive]
enum ValidationError {
Message(&'static str),
#[cfg(feature = "alloc")]
Error(alloc::boxed::Box<dyn core::error::Error + Send + Sync>),
}
Might be interesting to see what some common validation errors are in case more specialized ones could be added, either for better formatting or for richer errors in non-alloc environments (e.g. I went with an |
Here's the current draft in #188 (error reporting beyond #[derive(Facet)]
#[facet(invariants = "invariants")]
struct MyNonZeroU8 {
value: u8,
}
impl MyNonZeroU8 {
// TODO: does this need to be MaybUninit?
fn invariants(&self) -> bool {
self.value != 0
}
}
#[test]
fn struct_with_invariant() {
facet_testhelpers::setup();
let (poke, guard) = PokeValueUninit::alloc::<MyNonZeroU8>();
let poke = poke.into_struct().unwrap();
let poke = poke
.field_by_name("value")
.unwrap()
.set(0u8)
.unwrap()
.into_struct_uninit();
// This should fail because the invariant is violated (value == 0)
match poke.build::<MyNonZeroU8>(Some(guard)) {
Err(ReflectError::InvariantViolation) => {
// Expected failure due to invariant violation
}
Ok(_) => panic!("Expected invariant violation, but build succeeded"),
Err(e) => panic!("Expected invariant violation, got {:?}", e),
}
} Mutation is also an issue even once you get a fully initialized struct. For now, my solution is to go back to a |
Nice! There are at least 2 types of invariants:
I think we can handle
Something like: #[derive(Facet)]
#[facet(invariants = "invariants")]
struct MyByteVec {
// Safety invariant: `ptr` points to `len` bytes of memory
ptr: *mut u8,
len: usize,
}
impl MyByteVec {
fn invariants(&self) -> bool {
false
// or maybe:
// panic!("invariants for MyByteVec can't be checked at runtime")
}
}
#[test]
fn my_byte_vec() {
facet_testhelpers::setup();
let ptr = Box::into_raw(Box::new(123u64));
let len = std::mem::size_of::<u64>();
let (poke, guard) = PokeValueUninit::alloc::<MyByteVec>();
let poke = poke.into_struct().unwrap();
let poke = poke
.field_by_name("ptr")
.unwrap()
.set(ptr)
.unwrap()
.into_struct_uninit();
let poke = poke
.field_by_name("len")
.unwrap()
.set(len)
.unwrap()
.into_struct_uninit();
let v = unsafe { poke.build_no_invariants_check::<MyByteVec>(Some(guard)) };
let v = v.unwrap();
} |
I think so too. The other thing was mutation once something is already fully initialized. We don't want people to be able to just drop the poke and leave the original mutably borrowed value in some invalid state. I honestly don't know how to do this. I think the The version of |
#188 landed, and it doesn't even allow direct mutation, only building from uninitiated to fully initialized. Invariants can be checked by adding a |
Hi @fasterthanlime, I'm curious whether unsafe derives as in rust-lang/rfcs#3715 still be useful to you, or would they change the way you approached your implementation? Or did the way you ended up solving this feel unambiguously better to you? |
I think so? I'm not entirely sure. I'm still torn because.. deriving |
Yeah, violating internal invariants does not itself justify |
I think an important difference between The
Even if a type has invariants, I don't see how deriving I see three options:
I think 3 would be the best to prevent footguns. |
I think they both could be safe: when you’re deriving I’m not sure that’s the best solution though. The problem with the full reflection design like impl TypeImplementingFacet {
pub(crate) fn pub_crate() -> impl Facet;
fn priv() -> impl Facet;
} so to access information about private fields you have to go through |
As for 4., it cannot be done in safe code at all in facet. Right now you can do it in that you can get the offset of the field, but then to write to it, you have to resort to unsafe. So I stand by my argument that, with the new Wip interface (it wasn't the case before), deriving |
But what if I only want to derive |
True :) |
@tyilo @GoldsteinE what use-cases do you see for in-place mutation besides like.. debuggers? Which are inherently unsafe? It would be interesting if facet let crates annotate "this field is fine to change by yourself, here's the validation function" or something, but it's not... I feel It's not the primary objective. And it's not relevant to the discussion of whether deriving
So that settles that as far as I'm concerned — the rest I'm interested in discussing of course (but maybe separately).
Then (You can also just declare it as scalar/opaque and then there's no way to build it through Wip either.) |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Is it supposed to be safe to implement
Facet
with#[derive(Facet)]
for a type that has internal invariants?If so, then
facet_json::from_str
needs to be unsafe:The text was updated successfully, but these errors were encountered: