3 ms·
I take issue with the motivating example. pub struct Point { pub x: i32, pub y: i32, } I take it that the authors would wish it to be poss
by duped 4y ago
I take issue with the motivating example.
pub struct Point {
pub x: i32,
pub y: i32,
}
I take it that the authors would wish it to be possible for the consumers of the crate that defines this struct to also be consumers of `serde` and allow it to be serializable, even though the authors of `Point` have not marked the struct `#[derive(Serialize, Deserialize)]`.
This is not a good thing, because it means that consumers of `Point` have observable behavior not intended by the authors of `Point` and makes it possible for previously unbreaking changes to become breaking. For example, say `Point` now has state that gets cached:
pub struct Point<'a> {
pub x: i32,
pub y: i32,
cache: &'a Cache, // where cache is not serializable or observable
}
Under the current regime this is not a breaking change because the authors do not intend `Point` to be serializable and do not want its consumers to serialize it directly. Under the design of the authors it would be possible for other code to make bad assumptions about its inputs. This is perhaps a weak example, but say `Cache` contained functions or other non-serializable data. Now the versioning of the crate containing `Point` is meaningless.
The point is: `#[derive(Serialize, Deserialize)` is a good thing: it is not boilerplate. It is annotating the contract the code has with its consumers, and adding or removing from it is potentially a breaking change.
---
Reading a bit further: you need to expand on the reason the Orphan Rule is a Problem That Has To Be Solved.
While it's true that the resulting headaches are because of a conservative choice, the reason that we need to deal with it is because without such a constraint, future code may lead to ambiguity that cannot be resolved by the compiler or by a programmer. I don't see any reasoning in this post about how reflection would mitigate the same problem:
How do you prevent reflection from creating ambiguity in type/method resolution and avoid inadvertent breaking changes in the ecosystem?
Because the Coherence problem is the actual Problem To Be Solved.
---
There are a lot of interesting applications of compile-time introspection on code, and I've had the experience of writing a lot of metaprogramming with it (and runtime introspection!).
The problem is that this is mostly an anti feature, because you do not want to have to evaluate code to understand code. It makes programs harder to reason about and fundamentally more buggy. In a language designed around explicitness and correctness like Rust, the proposal is naturally going to meet deaf ears. This belongs in a LISP dialect more than Rust, imho (as one who loves both!)
- LegionMammal978 4y ago> Under the current regime this is not a breaking change because the authors do not intend `Point` to be serializable and do not want its consumers to serialize it directly. Does your example not show a breaking change? With the old version, all fields are public, so users can construct Point values themselves, and fully deconstruct them with pattern matching. With the new version, the struct has a private field, so users cannot construct Point values directly. (Also, it has an extra lifetime parameter which users must specify!) That is to say, the authors do not intend to allow access to private fields (they say that repeatedly), so something that wouldn't ordinarily be a breaking change would not become a breaking change in the presence of this feature. Any additional behavior defined by consumers must be built on top of the provider's contract. In this case, either the additional serialization behavior would have to require all fields to be public, or it would have to ignore any private fields. And additional deserialization behavior would similarly have to either require that all fields are public or only allow filling values into existing structs.
- Georgelemental 4y ago> Under the current regime this is not a breaking change It is. It would not be if the original `Point` definition was annotated with the `#[non_exhaustive]` attribute. Or actually, that still wouldn't be enough, because the new definition introduces a lifetime parameter. You could call the new definition `struct NewPoint` and introduce a `type Point = NewPoint<'static>` alias to preserve backward compatibility.
- deleted 4y ago[deleted]
- vlovich123 4y agoTIL. I’ve not seen this annotation on any rust crates I’ve looked at. Might have missed it of course but this feels like a niche annotation and everyone relies on semver instead.
- saghm 4y ago#[non_exhaustive] didn't get stabilized for downstream use until several years after Rust 1.0, which probably is why it isn't super commonly seen in the wild still, but I think the standard library has been using it since 1.0 for things like `std::io::ErrorKind`.