-
Notifications
You must be signed in to change notification settings - Fork 199
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
Deprecate public geometry fields #816
Comments
BTW if people are into this idea, I'm willing to do the grunt work. I should have time to complete it in the next several days. |
Yeah, I think we should do this as quickly as possible to give downstream crates as much time as possible to make the change, and maybe add a pinned issue about how to make the change? |
Agree, I am not a big fan of the public fields without accessors either, especially when we declare that values must not be NaN or inf without asserting that (possibly behind a feature flag). Let's make this happen asap to give a proper heads up. We should also mark the geometrycollection::new_from as depr too (?). |
Hiya @georust/core - this proposal to deprecate direct access to geo-type fields is likely to affect you. This is being done to soften the blow of an imminent breaking change to our struct fields in #797. Specifically, we're looking at deprecating this kind of code:
The other geometry fields would be similarly deprecated:
Please weigh in if you have thoughts or concerns. Or consider giving this issue a 👍 as a sign of life if it seems fine. You can see the full PR at #818 |
Why does field access like |
I agree My concern is that the struct initializers are widely used, and the error people will see is not helpful.
But to be clear, we don't need to change field access. If the cure of private fields is worse than the disease of confusing build errors, an alternative would be to just let code like |
Understood. IMHO ZM coords is such a big and breaking change so it should be easy to spot / fix the error on upgrade. Field access (not incl. intialization) seems so natural for Coord. For instance, |
@rmanoka i believe you are referring to nalgebra's coordinates.rs code. Seems magical, and I couldn't easily follow the logic there, but i'm all for doing that approach if it would allow seamless migration. |
I agree that it should be easy to spot - the code will not compile! And actually I think all the fixes are pretty easy if you already know what to do — just use The reason I opened this issue was because I saw there was pretty wide use of the I've never used I see some examples with field access, but I haven't seen any usage of the field-wise initializers. e.g.
I'm still digging in and trying to understand how it works - it might be a good compromise! |
I've been experimenting with this a bit. Deref is pretty wild! Here's a branch with private With that, when someone tries to build a field wise
But they'll still be able to:
Is that conceptually what you were envisioning @rmanoka? WRT legacy users, it seems like a slightly better error than #797: "missing fields What I failed to find was any way to actually gracefully deprecate (without error) the field wise initializer while still retaining undeprecated field access. I can't imagine how that would work. An incremental improvement maybe: We could take this opportunity to address #540, and have a helpful deprecation warning along with the error: - pub struct Coordinate<T: CoordNum> {
+ pub struct Coord<T: CoordNum> {
x: T,
y: T,
}
+ #[deprecated(
+ note = "use the new Coord struct with initializer: coord!(x: 1.0, y: 2.0) or Coord::new_xy(1.0, 2.0)"
+ )]
+ pub type Coordinate<T> = Coord<T>;
# rename _all_ internal usages to avoid deprecation warnings
- impl<T: CoordNum> Coordinate<T> {
+ impl<T: CoordNum> Coord<T> {
pub fn new_xy(x: T, y: T) -> Self {
Self { x, y }
}
} (fully applied here: https://github.com/georust/geo/compare/mkirk/deref-error-and-coord-deprecation?expand=1#diff-6ff837506e614f9a8a7c162d78e60b81e6020202c551fcb680b2b8867ba16e17R45) That'll still fail to compile legacy code, but the deprecation warning can at least reference the valid init methods:
|
This is very cool @michaelkirk ; thanks for trying out the ideas. The last approach to rename to Coord seems good to me. The field access for Coord alone could be done via some unsafety, and move back to vanilla pub fields (but all 4 coords) in the next breaking release. Is that what your suggesting? |
I also like the renaming approach. My biggest concern is the possibility (unconfirmed) of the tech debt we will have with the repr(C) + unsafe. repr(c) seems fairly straightforward for the simple usecases (numeric values + So basically it is a "dumb vanilla Rust approach" that forces |
I would actually suggest to keep the fields on People doing 2D coords would need to do something unpleasant like:
Otherwise they'd get the same unhelpful error:
So my suggestion is to keep them private, and double down on macro usage: And another thing to clarify - the breaking change would occur when making these fields private (because it would break people's ability to use field wise initializers). But then, from there, adding 3D would not entail another break. Am I thinking about that right? |
This is worth calling out. So long as all of our fields are the same size my understanding is there's nothing to be gained by the default repr over |
I don't think repr(C) is needed for the transmute. Only repr(transparent). And make Coord a wrapper around the inner fields we will expose via the deref-mut. |
I meant switch back if we want to clamp down on unsafe code or similar. I'm happy with keeping the deref-mut impl. too. |
Any idea why the |
I think this is more of a "compiler guarantee" question, i.e. I think it is bad to rely on "happens to work", we should rely on the explicit guarantees by the compiler that our usage of unsafe is "safe". Do we know that it is a valid usage? A straw man example - Rust could decide to sort fields in a struct based on how often each field is used, putting the most used one first so that access is potentially faster or because of a higher caching rate. |
The nomicon definitively lays this out as (currently) undefined: https://doc.rust-lang.org/nomicon/repr-rust.html
You raise a good apoint about |
How about something like this: https://gist.github.com/rmanoka/23cd9237abff92f41759e44222ebb301 pub struct Coord<T, Z = (), M = ()> {
xy: Coord2<T>,
z: Z,
m: M,
} Then we can use // Does not work
// let coord: Coord<_> = Coord2 { x: 0., y: 0. };
let mut coord = Coord::new(10., 20.);
coord.x -= 5.0;
assert_eq!(coord.x, 5.0); |
Oh right - no need for the unsafe transmute at all! I don't know why I immediately escalated to that. I guess because it felt "serious" 😆. Thanks for checking me @rmanoka! One last thing I noticed that I kind of liked about nalgebra, is how they only allowing access to the available fields. e.g. with #797 you can access NoValue on pt2d.z and pt2d.y, whereas in nalgebra I wonder if we can achieve something similar without too much knock on effect. Here's a little POC of what I mean: I'm sure applying it to the actual geo-types would be harder. But is it a good goal to remove access of z/m fields from 2d coords? |
This approach seem to break all functions that pass around |
Do you mean what was linked in my playground? That's not intended to be dropped into geo-types, but rather a simplified version to show what I mean by having a generic container. In geo-types, I think it'd have to look more like:
|
thx @michaelkirk , i hacked together a working example with your suggestion. Non-2D types can still be defined with the type alias. fn make_xy() -> Coord<i32> {
Coord::new_xy(1, 2)
}
fn make_xyz() -> Coord3D<i32> {
Coord::new_xyz(1, 2, 3)
} With this approach, Is it possible to define an algorithm once without doing it for each case e.g. I don't see how to have an algorithm that shifts points around in 2D space while preserving z/m values - regardless if z/m values are present or not? |
That's a good point. I'm not sure how it'd be possible without using either:
|
Excepting Rects and Polygons, all of our geometry structs have public fields.
This is going to make releasing #797 not only a breaking change, but a breaking change without any deprecation notice. Downstream crates, when updating will not be able to build because of this error:
That error message implies that the user should add an
m
andz
argument, which is probably not what they want. The probable fix is for them to use thecoord!
macro instead.But other than hoping that they track down the release notes or find the right docs on their own, there isn't much we can do to help them.
I know @nyurik went around and fixed some crates to use the
coord!
macro, but there seem to be plenty of other usages: https://github.com/search?l=rust&q=geo+Coordinate+%7B&type=Code (some false positives in there, but plenty of legit ones)In an ideal world, we'd precede any breaking change with a non-breaking release with helpful deprecation messages that guide the user to the right solution, such that if the consumer fixes all the deprecations, they can expect to update to the next breaking release without issue. This isn't always possible, but it is in this case.
Specifically, I'm proposing we do something like this for at least
Coordinate
(if not all theGeometry
enum members) in a non-breaking geo-types release to precede the breaking geo-types changes in #797.The downsides are that field access is easier to type. I bet some people appreciate the conciseness of being able to write
coord.x
rather thancoord.x()
.WDYT? Is it worth making this transition now? Are there other advantages to these fields being public?
The text was updated successfully, but these errors were encountered: