You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
T-lang discussed this in a backlog bonanza meeting today, and noted several concerns that should be addressed prior to moving forward:
Is there active usage/desire for this feature?
Tuple destructuring and/or combining may conflict with particular decisions made here (for a subset of types) -- which merits at least a more thorough exploration to make sure this concern is assessed (it may not be fully accurate).
This is OK. Async tasks are generally long-lived data structures. Therefore, the extra allocation is a very small added cost.
This Option version has a benefit: it lets us drop the completed future right away. With the bool version, the future drops after its last waker is dropped. Some applications may use timeouts that keep a waker for the duration of the timeout. That could keep future enclosures around for minutes, and increase memory pressure.
I just realized that the None and Some enum variants must be the same size. So the enum version wouldn't save memory used directly by the Future. It would save only heap-allocated memory owned by the Future. A completed future from an async closure cannot contain own any heap-allocated memory because it drops all variables before returning and completing. Other types that implement Future may have heap-allocated memory, but those types are rarely used as tasks. Therefore, an enum alone provides almost no memory savings because it saves only heap-allocated memory, and completed tasks have none of that.
However, this edited version works (avoids deconstruction of the unsized parameter, or more precisely merely avoids creating a let binding to the unsized part).
We discussed this some in the Rust-for-Linux sync meeting. This is impacting stabilization of #[derive(CoercePointee)]. There doesn't seem to be a strong rationale in favor of the feature (the argument for is essentially "why not, it works for structs") and there are some plausible responses ("so we have more freedom to play with tuples"). Therefore my inclination is to say "let's just remove this unstable feature" (we also considered trying to stabilize it, but it doesn't seem worth it to me).
It's also a 2-way door, of course, we can always add it back.
I also don't think this feature really pulls its own weight.
For structs, you are only forced to lay out the last field at the end when the last field is a generic parameter that is ?Sized. Those conditions pretty much coincide with exactly the structs where you want to make use of unsizing coercions. However, tuples are always forced to apply this constraint to the layout just because this feature exists on unstable Rust, just because there could be a crate in the crate-graph that uses #![feature(unsized_tuple_coercion)].
Besides, unsizing tuples has seen basically no use since it was added in 2017. It seems unlikely that anyone will push this to be stabilized any time soon.
I tend to agree that we've been doing fine without tuple unsizing; unsized types aren't even that common and defining your own ADT for them isn't an undue burden -- given, as you say, that this unlocks more layout optimizations for tuples.
However, we could want other unstable unsizing coercions in the future, and it seems we are closing the door for that.
Niko mentioned in the meeting that this approach might be able to unblock future unstable impls. Though I tend to agree with CE's point that keeping around logic for doing unstable impls in one place and having various extra complexity to avoid leaking it seems like a bad state to be in.
I am in favor of just removing this feature. Tuple unsizing coercions inhibits future layout optimizations for tuples:
The last field of tuples would always have to be last in the actual layout as any tuple may participate in unsizing.
// the actual layout of the tuple must not be `(u16, u16, u32)` as (u16, u32, dyn Send)`// needs `dyn Send` to be at the end of the tuple.let x:&(u16,u32,u16) = &(1,2,1);let y:&(u16,u32,dynSend) = x;
This is not necessary for size optimizations as that optimization can always keep the last field in place; it doesn't matter where in the struct we pair values with matching alignment. It would inhibit other optimizations, e.g. @m-ou-se mentioned
if you sort them by alignment from big to small you can avoid padding bytes in the middle though, moving them all to the end
It may also inhibit optimizations based on the "preferred alignment".
For struct unsizing this only inhibits these optimizations for structs which can be unsized (have a ?Sized param in the last field), but given that all tuples may get unsized, we would be forced to limit these optimization for all tuples.
Activity
steveklabnik commentedon Mar 12, 2019
Triage: no changes
Mark-Simulacrum commentedon Jan 26, 2022
T-lang discussed this in a backlog bonanza meeting today, and noted several concerns that should be addressed prior to moving forward:
dhardy commentedon Jan 27, 2022
I subscribed to this because I wanted to use it once... but didn't because it's unstable and not too hard to work around. I suspect the same goes for many non-trivial projects. Searching GitHub shows 1391 code results, most of which appear to be tests. There are a few uses on the first page: https://github.com/aschaeffer/inexor-rgf-application, https://github.com/aschaeffer/rust-ecs-poc, https://github.com/libdither/disp, https://github.com/icostin/halfbit (feature = "nightly"); everything after that appears to be tests (I checked 10 more pages).
In short: some possible utility, but not a priority.
mleonhard commentedon Mar 27, 2022
I wish to use this in safina-executor library. The library currently uses this type for async tasks:
(v0.3.2 lib.rs:306)
I am modifying it to remember when a task has returned
Polling::Readyand avoid polling it again.I would like to use the following type, but it depends on this
unsized_tuple_coercionfeature:Instead, I will use this type, which requires two allocations per task:
This is OK. Async tasks are generally long-lived data structures. Therefore, the extra allocation is a very small added cost.
This
Optionversion has a benefit: it lets us drop the completed future right away. With theboolversion, the future drops after its last waker is dropped. Some applications may use timeouts that keep a waker for the duration of the timeout. That could keep future enclosures around for minutes, and increase memory pressure.SimonSapin commentedon Mar 27, 2022
To use
boolon stable, does using a struct type instead of a tuple work?mleonhard commentedon Mar 28, 2022
@SimonSapin A struct works!
I think I'm going to keep the
Option<Box<_>>version because of the memory savings I mentioned above. Using an enum didn't work:mleonhard commentedon Mar 28, 2022
I just realized that the
NoneandSomeenum variants must be the same size. So the enum version wouldn't save memory used directly by theFuture. It would save only heap-allocated memory owned by theFuture. A completed future from an async closure cannot contain own any heap-allocated memory because it drops all variables before returning and completing. Other types that implementFuturemay have heap-allocated memory, but those types are rarely used as tasks. Therefore, an enum alone provides almost no memory savings because it saves only heap-allocated memory, and completed tasks have none of that.QuineDot commentedon Feb 15, 2023
There's an argument to be made for consistency. You can unsize coerce, deconstruct, and otherwise match against custom tuple types already. You can also do all these things on stable with built-in tuples too, given a touch of
unsafe. 1Footnotes
I do recognize that's still unsound without
offset_of. On the other hand, there's no other reasonable layout in this case. ↩dhardy commentedon Feb 15, 2023
@QuineDot no you can't. You are merely using references to unsized types.
However, this edited version works (avoids deconstruction of the unsized parameter, or more precisely merely avoids creating a
letbinding to the unsized part).nikomatsakis commentedon Feb 26, 2025
@rustbot label +I-lang-nominated
We discussed this some in the Rust-for-Linux sync meeting. This is impacting stabilization of
#[derive(CoercePointee)]. There doesn't seem to be a strong rationale in favor of the feature (the argument for is essentially "why not, it works for structs") and there are some plausible responses ("so we have more freedom to play with tuples"). Therefore my inclination is to say "let's just remove this unstable feature" (we also considered trying to stabilize it, but it doesn't seem worth it to me).It's also a 2-way door, of course, we can always add it back.
Nominating to give lang a heads up about this.
3 remaining items
Darksonn commentedon Feb 27, 2025
I also don't think this feature really pulls its own weight.
For structs, you are only forced to lay out the last field at the end when the last field is a generic parameter that is
?Sized. Those conditions pretty much coincide with exactly the structs where you want to make use of unsizing coercions. However, tuples are always forced to apply this constraint to the layout just because this feature exists on unstable Rust, just because there could be a crate in the crate-graph that uses#![feature(unsized_tuple_coercion)].Besides, unsizing tuples has seen basically no use since it was added in 2017. It seems unlikely that anyone will push this to be stabilized any time soon.
RalfJung commentedon Feb 27, 2025
I tend to agree that we've been doing fine without tuple unsizing; unsized types aren't even that common and defining your own ADT for them isn't an undue burden -- given, as you say, that this unlocks more layout optimizations for tuples.
However, we could want other unstable unsizing coercions in the future, and it seems we are closing the door for that.
Darksonn commentedon Feb 27, 2025
Niko mentioned in the meeting that this approach might be able to unblock future unstable impls. Though I tend to agree with CE's point that keeping around logic for doing unstable impls in one place and having various extra complexity to avoid leaking it seems like a bad state to be in.
Auto merge of rust-lang#137728 - Darksonn:no-tuple-unsize, r=<try>
lcnr commentedon Feb 28, 2025
I am in favor of just removing this feature. Tuple unsizing coercions inhibits future layout optimizations for tuples:
The last field of tuples would always have to be last in the actual layout as any tuple may participate in unsizing.
This is not necessary for size optimizations as that optimization can always keep the last field in place; it doesn't matter where in the struct we pair values with matching alignment. It would inhibit other optimizations, e.g. @m-ou-se mentioned
It may also inhibit optimizations based on the "preferred alignment".
For struct unsizing this only inhibits these optimizations for structs which can be unsized (have a
?Sizedparam in the last field), but given that all tuples may get unsized, we would be forced to limit these optimization for all tuples.Rollup merge of rust-lang#137728 - Darksonn:no-tuple-unsize, r=oli-obk
Unrolled build for rust-lang#137728
Rollup merge of #137728 - Darksonn:no-tuple-unsize, r=oli-obk
Rollup merge of #137728 - Darksonn:no-tuple-unsize, r=oli-obk
Rollup merge of #137728 - Darksonn:no-tuple-unsize, r=oli-obk
nikomatsakis commentedon Mar 12, 2025
This PR landed which removes this feature:
#137728
I'm going to close this tracking issue as a result.
Rollup merge of rust-lang#137728 - Darksonn:no-tuple-unsize, r=oli-obk
Darksonn commentedon Jul 29, 2025
I submitted a reference PR as a follow-up to this removal: rust-lang/reference#1952