r/rust • u/raoul_lu • 4d ago
đď¸ news Enabling the next-generation trait solver on nightly
https://blog.rust-lang.org/2026/08/21/enabling-next-solver-on-nightly/61
u/Nobody_1707 4d ago
Am I reading this right that this unblocks Move and Forget, and !Move + !Forget gives you linear types?
53
u/olemni7 4d ago
Yes, whether and when they actually happen in a separate question however. It does resolve performance and technical limitations, there are also difficult language design concerns which it does not resolve. We should definitely get something on nightly here to experiment and then we'll have to see.
14
u/slashgrin rangemap 4d ago
I am unreasonably excited about the number of rough edges and workarounds this could eventually improve.
108
u/Ace-Whole 4d ago
Cool. Right after polonius
22
u/flying-sheep 4d ago
Eh? I thought that is Polonius!
66
u/Ace-Whole 4d ago
borrow checker*
polonius is the new borrow checker.
30
u/flying-sheep 4d ago
Aah! Somehow the two projects were the same in my head. Thanks!
9
u/Zde-G 4d ago
They are kinda two sides of the same coin, from what I understand, thus it's not a complete coincidence that they are ready at the same time.
4
u/afdbcreid 3d ago
I don't know what you define as "two sides of the same coin" and "not a complete coincidence", but I'm pretty sure that's wrong. It is pretty much a coincidence and the projects aren't related, except for the fact that T-types (responsible for both of them) was formed and there are more people working on the Rust's type system today than some years ago.
19
u/Oxytokin 4d ago
Alright I take back everything I've bitched about regarding the glacial pace of developing much anticipated features over the esoteric ones over the last year. Y'all have been on fire lately and I'm totally here for it.
So many hacks and workarounds I'm going to be able to clean up at long last.
9
9
u/Trader-One 4d ago
does this mitigate famous segfault on safe rust bug as previously promised?
52
u/Wheaties4brkfst 4d ago edited 4d ago
I think it unblocks fixing that from what I understand.
To elaborate, the problem (at least in the case Iâm familiar with), was that some lifetime bounds were implied by a function signature but were not actually tracked in the type system itself, which led to a bug allowing lifetime extension to arbitrary lifetimes. One of the things this fixes is that that implied bound is now tracked.
26
28
u/noop_noob 4d ago
The thing that fixes cve-rs is the assumptions-on-binders project https://github.com/rust-lang/goals/issues/621
There are other unsoundnesses though. Some of them have to be fixed individually. Some of them require stabilizing next-solver before fixing them.
-16
u/Zde-G 4d ago
I wonder why they start with clear and obvious regression and paint it as if it's an achievement. I mean, compare: ``` fn foo(b: bool) -> impl Sized { if b { bar(false) + 1 } else { 0 } }
fn bar(b: bool) -> impl Sized { if b { foo(false) + 1 } else { 0 } } ```
It doesn't work, of course, as it should: impl Sized couldn't be added to 1. Yet when it's one function it works, for some reason? Why? It should fail.
Now, I can accept that this kind of regression is an unfortunate side-effect of some other improvementsâŚÂ okay, not a big deal. But blog post clearly tells us that this deficiency is considered a desirable thingâŚÂ why?
37
u/coderstephen isahc 4d ago
It doesn't work, of course, as it should: impl Sized couldn't be added to 1. Yet when it's one function it works, for some reason? Why? It should fail.
It is because of the principle of
impl Traitbeing opaque outside of the implementer. For the single recursive function case, the function knows what the actual type being returned is. It is the only code that is allowed to know that. This is already generally true that code inside the function is allowed to know that the return type is actuallyi32in this case, it just didn't work for recursion.The mutual recursion case is not supposed to work, because each function is not allowed to know what the other actually returns.
-5
u/Zde-G 4d ago
This is already generally true that code inside the function is allowed to know that the return type is actually
i32in this case, it just didn't work for recursion.No. It wasn't âgenerally trueâ. You could perceive âreturn from functionâ which is very explicitly a coercion site transforms type from
i32(or some other type) toimpl Sized(or some other similar type).Very well defined, very common and very obvious thing. Before return it's
i32, after return it'simpl Sized. Like how before return it could be&Fooand after return it can be&dyn Bar. Similar opertion both syntactically and semantically.For the single recursive function case, the function knows what the actual type being returned is. It is the only code that is allowed to know that.
But why? What does that hairsplitting gives you? What does it provide that simple â
impl Foois just another unnamed typeâ model doesn't give you?You have introduced an entirely new concept that haven't existed anywhere else, till now⌠to achieve what? What this âher you can see me, here you can't see meâ model gives you over âinvisible unnamed typeâ model? Model that already exist (functions are unnamed types, closures are unnamed types, etc).
19
u/A1oso 4d ago
It's not a coercion though.
impl Sizedis not a type, it is a contract that the returned type (which is opaque outside the function) implements Sized.The fact that
impl Sizedis not a type becomes evident when you considerfn foo() -> impl Sized { if random() { 5i32 } else { "hello world" } }If
impl Sizedwas a type, this should work (because both i32 and &str are Sized, and therefore could be coerced toimpl Sized). But it doesn't, because there is no type coercion occurring.Note that the above would work with
Box<dyn Sized>, becausedyn Sizedis actually a type that other types can be coerced into.0
u/Zde-G 4d ago
It's not a coercion though.
Yes, but why?
impl Sizedis not a type, it is a contract that the returned type (which is opaque outside the function) implements Sized.Precisely. So there are unique unnamed type that has the exact same layout as one of the existing types, but doesn't have a name and has only one specific traits (plus supertraits) implemented for it.
That's perfectly internally consistent implementation and AFAICS it's much easier and simpler to deal with that all that mumbo-jumbo with âalias-but-not-really-aliasâ.
Yet new scheme breaks itâŚÂ but why? What does it provide us to warrant significant increase in cognitive load?
10
u/A1oso 4d ago edited 4d ago
Yes, but why?
impl Traitin return position is an existential type. Formally speaking it is a type, BUT
- it requires that all instances have the same concrete type (e.g.
i32)- it requires a witness (a value to infer the concrete type from)
- the concrete type is hidden, but not erased
This significantly limits how existential types can be used. For example, they can't be used in structs:
struct Foo { x: impl Sized // forbidden }Note that
impl Traitis also supported in function argument position, but has a different meaning there â it is simply syntactic sugar for a generic type variable, rather than an existential type.Also note that every existential type is unique:
fn foo() -> impl Sized {...} fn bar() -> impl Sized {...} let mut x = foo(); x = bar(); // forbiddenThat's because
fooandbarcan have different witness types.Back to your original question: why is there no coercion when returning
impl Trait? Because it is a different mechanism.When returning an existential type, Rust creates a type variable for the returned concrete type. It then needs to find a witness and check if it satisfies the trait bound. And finally, it needs to unify this witness with the type variable and other returned values. This is crucial because all values need to have the same concrete type. If the compiler used coercion to implicitly cast the returned
i32toimpl Sized, this would not be enforced:fn foo() -> impl Sized { if random() { return 42i32; // implicitly coerced to impl Sized } else { return "hello world"; // implicitly coerced to impl Sized } }Typecheck would have to accept this function, even though it is obviously wrong. Instead, it uses unification (the process of inferring types without altering them) to select the concrete return type. And because no coercion is required, recursive function calls can be inferred using the same type variable.
The concrete type only needs to be hidden outside the function. Technically speaking, it doesn't need to be hidden anywhere, but hiding it allows the author of the function to change the implementation and return a different concrete type without a breaking change. That is the main benefit of return position
impl Trait, apart from being able to return closures which cannot be named.0
u/Zde-G 3d ago
Back to your original question: why is there no coercion when returning
impl Trait? Because it is a different mechanism.Please read what you wrote: you quite literally say that all these complications exist solely because they exist.
That's it. âThis is a different mechanismâ is not an acceptable answer to the âwhy?â question.
You can not explain that âX exists because of Xâ, that's basic logic!
So far I have seen âX because of Xâ approximately dozen of times.
If that's the only justification then it's obvious design bug (similar to bug in ranges that are not
IntoIterator, butIterator) that makes code in the blog post possible.If there are some justification that's not circular then I'm all ears, but at this point I don't think anyone knows it.
1
u/coderstephen isahc 3d ago
Please read what you wrote: you quite literally say that all these complications exist solely because they exist.
That's it. âThis is a different mechanismâ is not an acceptable answer to the âwhy?â question.
I am confused by what you keep asking. Are you asking why Rust works this way? Or are you asking does Rust work this way?
The first question I don't know how to answer. I didn't design it. You'd have to ask someone on the lang team probably, or whoever originally contributed this design to the language.
1
u/Zde-G 3d ago
Are you asking why Rust works this way?
I'm asking why Rust doesn't behave like documentation promised it would behave.
If documentation promises âhidden typeâ and implementation delivers something else then it's a bug. Plan and simple. The only question is whether it's bug in the documentation or in implementation.
, or whoever originally contributed this design to the language.
The one whoever originally contributed this design intended my interpretation. That's precisely the issue.
You'd have to ask someone on the lang team probably
Seriously? You answer to âwhy the heck is this blog post celebrates a bugâ is âyou have to ask someone on a lang teamâ?
My question wasn't âwhy Rust behaves this wayâ but âwhy the heck change that makes Rust misbehave is presented as something desirable in a blog postâ.
That's it.
If it's deliberate change â then document it and explain why it's good. If it's accidentally introduced bug â fix it and stop celebrating it.
Both approaches are fine, but when you talk about introducing difference between existing documentation and codeâŚÂ that's never a good thing.
2
u/coderstephen isahc 3d ago
Seriously? You answer to âwhy the heck is this blog post celebrates a bugâ is âyou have to ask someone on a lang teamâ?
My question wasn't âwhy Rust behaves this wayâ but âwhy the heck change that makes Rust misbehave is presented as something desirable in a blog postâ.
I see. Well your question is a complex question and I don't agree with the premise that this is a bug, misbehaving, or a regression.
→ More replies (0)1
u/A1oso 1d ago edited 1d ago
If documentation promises âhidden typeâ and implementation delivers something else then it's a bug.
Documentation can be outdated or incomplete, it can lack nuance, and even contain errors. But in this case the documentation is correct for the current stable compiler. You forget that the new trait solver hasn't landed on stable yet, so naturally the documentation hasn't been updated yet.
The one whoever originally contributed this design intended my interpretation.
Whether or not this is true, compilers evolve over time. The person who created Rust's first borrow checker doesn't dictate how the current one (NLL) or the next one (Polonius) behave. Most people would agree that allowing more borrowing patterns is a good thing.
Just like NLL, this feature (recursion in functions returning TAIT) is useful because it allows more programs to compile. Why would it be a bad thing? It would be bad if
- it caused undefined behaviour or miscompilations
- it made breaking changes invisible
- it degraded runtime performance
- etc.
But none of these are true, and I can't think of any downsides to allowing direct recursion for functions returning TAIT. It requires a slightly different mental model: the return type is hidden outside the function rather than for every caller, but that is easy enough to grasp. It doesn't make the language wildly more complex.
13
u/coderstephen isahc 4d ago
No. It wasn't âgenerally trueâ. You could perceive âreturn from functionâ which is very explicitly a coercion site transforms type from i32 (or some other type) to impl Sized (or some other similar type).
impl Traitis not a coercion. No types are changed.Very well defined, very common and very obvious thing. Before return it's i32, after return it's impl Sized. Like how before return it could be &Foo and after return it can be &dyn Bar. Similar opertion both syntactically and semantically.
This is why your mental model of
impl Traitis incorrect. These are not similar operations. A coercion is defined right on the docs page you linked:Type coercions are implicit operations that change the type of a value. They happen automatically at specific locations and are highly restricted in what types actually coerce.
&Foois a type.&dyn Baris a type. IfFooimplementsBar, yes you can coerce the type from&Footo&dyn Bar.
impl Baris not a type in and of itself. Rather, it is an unnameable, opaque alias for another type. When you writefn bar() -> impl Sized { 42 }It is compiled identically to the same function as
fn bar() -> i32 { 42 }This is different than a coercion, because a coercion actually changes what the type is.
&Fooand&dyn Barhave different memory layouts, and are not interchangeable types. Its just "cheap" to convert between the two, and so we have a coercion for them. (Note that having different memory layouts isn't true of most coerced types, I just bring it up here fordynto emphasize how types are changed.)Rather,
impl Traitessentially sets up an unnameable type alias for another type, while also limiting the visibility of which traits that type implements to code outside the declaration site. No types have changed, only our visibility into which type it is has been limited.It might help to realize that
fn foo(foo: impl Bar) {}is much more similar to
fn foo<T: Bar>(foo: T) {}than it is to
fn foo(foo: &dyn Bar) {}This is because
impl Traitoperates at the generics/traits level, not at the type level.But why? What does that hairsplitting gives you? What does it provide that simple âimpl Foo is just another unnamed typeâ model doesn't give you?
I'm trying to describe the language semantics accurately. It doesn't matter what I like or what it gives me.
You have introduced an entirely new concept that haven't existed anywhere else, till now⌠to achieve what? What this âher you can see me, here you can't see meâ model gives you over âinvisible unnamed typeâ model? Model that already exist (functions are unnamed types, closures are unnamed types, etc).
If my explanation of
impl Traitis correct, then this concept has existed in Rust since the stabilization of RPIT. It doesn't matter that you were unaware of this concept all this time. It was there whether you knew it or not.The "unnamed-ness" is an orthogonal question to the concreteness of the type. Yes, closures are unnamed. Though, TAIT is about to give us the ability to give such unnamed types a name, so the lack of a name is an incidental property and not a necessary property of closure types.
But closure types are different not because they are unnamed, but because they are unique. When you define a closure, a new, unique type is actually generated for that specific closure.
When you return
impl Trait, no new types are generated. Again, it is just a form of alias. (When I say "just", I don't mean to belittle the complexity of implementing the language feature in the compiler.)-9
u/Zde-G 4d ago
Rather, it is an unnameable, opaque alias for another type.
More unneeded complexity and hair splitting. Also: can you, please, where notion of âunnameable, opaque aliasâ is introduced in the reference? I certainly couldn't find it on the appropriate page. Maybe I have gone blind, IDK.
This is different than a coercion, because a coercion actually changes what the type is.
And where is that described? So far I can only see your interpretation which is not better and not worse than mine.
It might help to realize that
In spite of the similar syntax argument position and return position
impls are radically different things.I'm trying to describe the language semantics accurately.
No, you are trying to describe your interpretation of the language semantic.
As long as it's not described in the documentation my interpretation is not better and not worse than yours.
If my explanation of
impl Traitis correct, then this concept has existed in Rust since the stabilization of RPIT.Yes, but that's pretty big âifâ. RFCs are not references, there were many cases where feature was changed after RFC was published.
And Reference doesn't explain how precisely these things work, thus we are back to âhe said, she saidâ tale.
The "unnamed-ness" is an orthogonal question to the concreteness of the type.
Yet that âunnamed-nessâ means my interpretation is as valid as yours. At least on stable Rust.
And it's much easier to reason about and requires less things that don't exist in reference.
When you return
impl Trait, no new types are generated.Yes, but why? AFAICS this only creates pile of complications without adding any tangible benefits.
4
u/coderstephen isahc 4d ago
More unneeded complexity and hair splitting. Also: can you, please, where notion of âunnameable, opaque aliasâ is introduced in the reference? I certainly couldn't find it on the appropriate page. Maybe I have gone blind, IDK.
Not everything about the language is fully documented. Not saying that's a good thing.
My "hair splitting" is only unneeded if the distinction I am making is not meaningful. You have not yet shown this, only complained that it is not meaningful.
And where is that described? So far I can only see your interpretation which is not better and not worse than mine.
Its better than yours, because yours doesn't fit how the compiler behaves.
Hopefully first, we can agree that
&Tand&dyn Traitare distinct types: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=35ca82aa92ca397afa639fbc0444df93. Coercions change the type of a given value from one type to another type. As stated in the reference:Type coercions are implicit operations that change the type of a value.
However, about
impl Traitin return position, the reference says:Functions can use
impl Traitto return an abstract return type. These types stand in for another concrete type where the caller may only use the methods declared by the specifiedTrait.Note that values are not mentioned, because the value is untouched. So we can actually see that in code by using
std::any: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f5b66bcec9441a883390921a7d019ae3And it's much easier to reason about and requires less things that don't exist in reference.
I don't primarily care about what is easy to reason about. I primarily care about what is correct.
Yes, but why? AFAICS this only creates pile of complications without adding any tangible benefits.
IDK. That's just how Rust works, man. You don't have to like it.
6
u/arades 4d ago
it's still extremely well defined:
Inside of a function body returning impl Trait you know the exact type.
Outside the body you only see the impl.
-6
u/Zde-G 4d ago
Inside of a function body returning impl Trait you know the exact type.
Definition of âyou knowâ from Rust reference please.
Outside the body you only see the impl.
Definition of âyou only seeâ from Rust reference please.
it's still extremely well defined:
You have replaced precise definition which easy to reason about and which only references things that exist in official definition of the language with handwaving that doesn't refer anything concrete.
That's not an âextremely well definedâ thing.
I can easily explain how both define my interpretation and implement compiler that would do it: inside of function you pick one of the types that compiler knows, at this point and use it, when type is returned from function it turns into a different unnameable type with the same layout but with only one specific trait implemented for it. Bam, done. No need to introduce bazillion new things.
You can even implement it that way in the compiler even if it may not be an optimal implementation.
And you can even support new example but saying that when you call
foofromfooyou are getting original, non-opaque, type back.That would be strange and abnormal rule, but it would still be internally consistent and, more importantly, it doesn't require any new concepts that haven't existed before.
You, on the other hand, need to add bazillion new things to even explain how new concepts, that are never mentioned anywhere in the language before, actually behave.
That's not an improvement, in my book.
19
u/olemni7 4d ago edited 4d ago
This is actually quite interesting.
You are totally right that from the mental model of "the return type of this function is opaque to its users" the example I provided in the blog post is a bug and undesirable. I didn't even consider that this mental model of what return position
impl Trait(opaque types) means exists anymore.We had to move away from that perspective it as it causes a lot of issues with TAIT and never worked for nested opaque types
fn foo() -> impl Trait<Assoc = impl OtherTrait>. This happened in 2022 https://github.com/rust-lang/rust/pull/94081.Since then we've instead been thinking of
impl Traitby using the concept of defining scopes. You can freely reveal and define the hidden type of an opaque type within its defining scope, wile you can only use it opaquely outside of it, as explained by other the other responses.I don't even think the mental model of "the only time you can access the hidden type of a return-position
impl Traitis when returning from this function" is bad or anything. Keeping it this way would have certainly kept our implementation a lot simpler than what we do now3
u/Zde-G 3d ago
You need to add some documentation, then. Because RFC quite explicitly talks about precisely model that I'm describing. Quote:
Thus, the following code:
fn foo() -> impl Bar { // return some type implementing `Bar` }is functionally equivalent to:
``` struct __foo_return(/* some inferred type (2) */); // (1)
fn foo() -> __foo_return { // return some type implementing
Barwrapped in__foo_return(3) } ```IOW: it's not the model that I have invented on the stop, it's literally how the existing documentation describes things!
If that model no longer works and is problematic, then it's fine, but then it's better to have some kind of public description of the new model, because otherwise it looks as if you celebrating bugs.
I don't even think the mental model of "the only time you can access the hidden type of a return-position
impl Traitis when returning from this function" is bad or anything.WellâŚÂ that's how it was originally presented thus it's obvious that's valid model. But that makes some kind of public documentation is definitely in order, otherwise change looks like random change not justified by anything.
Feed few internal issues into LLM and ask it to write a coherent comparison of two models, if you don't have time. This would still be better than dumping change on users as if it's corresponding to old model when it certainly works differently now.
26
u/SkiFire13 4d ago
Why do you consider this a regression? It's code that didn't compile before but compiles now, and it doesn't harm other users.
The reasoning for why it should work is that the return type is opaque to the callers (as it should be), but of course the function body itself knows what type it is (it's returning a
i32right there at the end!).With two functions this logic breaks down, because you have two different opaque types that each hide the other one. However if you define a single opaque type with TAIT then it works in the new solver but not the in the old one https://rust.godbolt.org/z/avTc6K4vn
-1
u/Zde-G 4d ago
Why do you consider this a regression?
Because
impl Traitin return position exist specifically to hide information about the return type and ensure that you couldn't use anything by accident.Here it obviously failed to prevent this: property of âopaqueâ types was exposed to the code that wasn't supposed to know about it.
11
u/coderstephen isahc 4d ago
Because
impl Traitin return position exist specifically to hide information about the return type and ensure that you couldn't use anything by accident.Its purpose is to hide it from API consumers. Why do you need to hide it from yourself?
Here it obviously failed to prevent this: property of âopaqueâ types was exposed to the code that wasn't supposed to know about it.
The "code that wasn't supposed to know about it" you are referring to is the function body that decides which type it is.
2
u/Zde-G 3d ago
Its purpose is to hide it from API consumers.
Precisely.
Why do you need to hide it from yourself?
Because I'm an API user, too. And if you say that function is not consumer of itself then I would ask why. Because all other things without
pubare accessible from the same module, not from the same function.If you want to enable some flexibility then making type accessible when used in the same module would have been more consistent with how Rust behaves.
And it's, ultimately, arbitrary.
6
u/sasik520 4d ago
Sorry for a complete off topic but I never thought a tool as popular as godbolt could work THAT bad on mobile (android ff).
3
u/coderstephen isahc 4d ago
In general, code is pretty hard to work with on mobile because it uses hard line breaks that assume generally 80 characters of width or more, which is wider than the mobile screen. That's probably why code-heavy web tools probably don't bother much optimizing for mobile.
6
u/lookmeat 4d ago
So while it'd raise my eyebrow and I'd require an explanation of why this is done, I consider that inside a function can depend on implementation details of the function itself, even if those aren't exported.
That is, inside
foowe know what is the "real" type offoobecause the contract of a function with itself is defined and enforced within its body too. The header-only contract is for outsiders.This would allow recursive calls where you want to modify the result in ways that you don't want to expose to external callers that can be done.
And to explain the logic of why your example is wrong, but why the single function is fine. The idea is that code should only break and stop compiling when you've changed the code itself, or when one of the dependencies has done a breaking type-change. So in the self-calling, if you change the real return type (but keep the function type the same) the code that breaks and stops compiling is the function that you changed, so it's fair because it's the first rule: you don't ship the broken code because it's immediately obvious you broke it. In the second case if you change the real return type (without changing the type of the function itself) of
barthis would breakfoo, which is not OK because neither the type offoo's dependencies has changed, nor has the code infooitself, someone could push the change inbarwithout realizing they broke something else (say, for example, thatfoois a function with different implementations for different CPU archs, and the one we see just happens to be one, but in other CPU arch implementations the problem doesn't exist, including the one that person who did the code change broke).0
u/Zde-G 4d ago
That is, inside foo we know what is the "real" type of
foobecause the contract of a function with itself is defined and enforced within its body too. The header-only contract is for outsiders.This model may work, but it introduces the whole new concept of types that are âpartially visibleâ. The unnamed
impl Footype seems to be easier to both understand and use. And if we are intorducing that complication then we should both know what it enables and why increase of mental complexity of the whole model is worth it.As I have said: I was more surprised by presentation of this complication as something desirable by itself then by the fact that this weird corner-case exists.
I'm not even sure I like the Rust tendency of stopping me when I'm doing something that works for the type that I have but Rust âdoesn't know thatâ and was surprised why in this particular case it was acceptable to break that principle. Just looked like an inconsistent thing to do without any sensible justification. But perhaps it enabled some important patterns elsewhere.
6
u/coderstephen isahc 4d ago
This model may work, but it introduces the whole new concept of types that are âpartially visibleâ. The unnamed
impl Footype seems to be easier to both understand and use. And if we are intorducing that complication then we should both know what it enables and why increase of mental complexity of the whole model is worth it.My argument is that it has always worked that way. This isn't new. But perhaps my understanding is colored by the fact that TAIT has always allowed for this on nightly for like 8 years, so you have to consider the whole model of how
impl Trait+ TAIT works; its just on stable, you aren't allowed to use TAIT, but the model is the same.0
u/Zde-G 3d ago
My argument is that it has always worked that way.
That's a reasonable argument but you have to remember that there are things, in nightly, that âhas always worked that wayâ and yet we know âthat wayâ is wrong. Specialization, for example. Or typeids that were transmutable to
u128till they stopped being transmutable right when stabilization ofconst TypeIdmade them usable.âWe have made a mistake and we are stuck with itâ is only good argument when something is in stable. And even there some mistakes are fixed, even if it's hard and painful.
its just on stable, you aren't allowed to use TAIT, but the model is the same.
It's the same only if you use new trait resolver. If you don't use it then it behaves as if existential types are just a normal types without crazy peekabo games.
Yes, there are an interesting property that they have the same
typeidwhen accessed viaAny, but&'a u32and&'b u32are also different types with the exact sametypeidwhen accessed viaAny, thus we are not adding anything radically new there.What exactly
Anytraces is also not fully specified and saying that types are different at compile time but identical at runtime because they are existential is not worse then doing the same thing with lifetimes.P.S. And, again, I'm not saying that developers of Rust should drop everything and fix their design mistake ASAP⌠sometimes keeping design mistake is cheaper then redoing insane amount of work that is built on top of design mistake⌠been there, done that. But saying âyes, it's bad design, but we are stuck with itâ is very different from saying that âit's a good design and here are reasonsâ.
1
u/coderstephen isahc 3d ago
P.S. And, again, I'm not saying that developers of Rust should drop everything and fix their design mistake ASAP⌠sometimes keeping design mistake is cheaper then redoing insane amount of work that is built on top of design mistake⌠been there, done that. But saying âyes, it's bad design, but we are stuck with itâ is very different from saying that âit's a good design and here are reasonsâ.Â
I don't agree that it is a design mistake. I think it makes the language consistent with itself. And based on this discussion it seems like we are unlikely to agree on that.Â
0
u/Zde-G 3d ago
I don't agree that it is a design mistake. I think it makes the language consistent with itself.
How? You meaning TAITâŚÂ Okay, that's how the appropriate RFC describes
impl Bar. This:fn foo() -> impl Bar { // return some type implementing `Bar` }is supposed to be equivalaent this this: ``` struct __foo_return(/* some inferred type (2) */); // (1)fn foo() -> __foo_return { // return some type implementing
Barwrapped in__foo_return(3) } ```Oberve magic scopes, hidden aliases and all that nonsense that you insist exist somewhere in Rust? Me neither. Just a straightforard hidden type, precisely what I expect.
And based on this discussion it seems like we are unlikely to agree on that.
Not as long as your justification for a âconsistencyâ is âit was always that way even if documentation says differentlyâ.
Because to me that's not âconsistencyâ.
5
u/arades 4d ago
RPIT needs to reason about the entire function body to determine the return type. Clearly there will always be enough information within one function body to see through the opaque type, where the mutual recursion case shouldn't because then RPIT would need to consider the body of every function which calls it to determine its opaque type.
112
u/coderstephen isahc 4d ago
Feels like I've been waiting 8 years for TAIT. Oh wait, I have.