Ada does this too
Ada does this too
Posted Feb 12, 2025 20:14 UTC (Wed) by NYKevin (subscriber, #129325)In reply to: Ada does this too by khim
Parent article: Maintainer opinions on Rust-for-Linux
Posted Feb 12, 2025 20:30 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link]
Posted Feb 12, 2025 20:30 UTC (Wed)
by khim (subscriber, #9252)
[Link] (7 responses)
Isn't that how linear types would work in any other language, too? Precisely because of halting problem such systems have to reject certain programs that are, in fact, valid. That's what Rust does with references, too. And like usual you may solve the issue with If you would attempt to use this code you'll find out that biggest issue is not with this, purely theoretical, problem, but with much more practical issue: lots of crates assume that types are affine and so many code fragments that “should be fine” in reality are generating drops at failure cases. Not in sense “the compiler have misunderstood something and decided to materialize drop that's not needed there”, but in a sense “the only reason drop shouldn't be used at runtme here is because of properties that compiler doesn't even know about and couldn't verify”. Unwinding `panic!` is the most common offender, but there are many others. IOW: problem with linear types in Rust are not with finding better syntax for them, but with the decisions about what to do with millions of lines of code that's already written… and that's not really compatible with linear types.
Posted Feb 12, 2025 22:02 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link] (6 responses)
Posted Feb 12, 2025 22:18 UTC (Wed)
by khim (subscriber, #9252)
[Link] (5 responses)
But that's precisely what I'm talking about. If you would take this exact Linear types are easy to achieve in Rust… but then you need to rewrite more-or-less all I don't see how changes to the language may fix that. If you go with linking tricks then you can cheat a bit, but “real” linear types, if they would be added to the language, would face the exact same issue that we see here: compiler would need to prove that it doesn't need drop in exact same places and would face the exact same issues with that. IOW: changing compiler to make “real” linear types and changing the compiler to make If someone is interested in bringing them to Rust that taking that implementation that already exist and looking on the changes needed to support it would be much better than discussions about proper syntax.
Posted Feb 12, 2025 22:42 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link] (4 responses)
> IOW: changing compiler to make “real” linear types and changing the compiler to make static_assert-based linear types work are not two different kinds of work, but, in fact, exactly the same work.
Currently, the reference[1] says that this is allowed to fail at compile time:
if false {
I agree that, if a given type T is manifestly declared as linear, then the compiler does have to prove that <T as Drop>::drop is never invoked. But I tend to assume that no such guarantee will be provided for arbitrary T, because Rust compile times are already too slow as it is, so you will have to use whatever bespoke syntax they provide for doing that, or else it will continue to be just as brittle as it is now.
Speaking of bespoke syntax, the most "obvious" spelling would be impl !Drop for T{} (like Send and Sync). But if you impl !Drop, then type coherence means you can't impl Drop, and therefore can't put a const assert in its implementation. Maybe they will use a different spelling, to allow for (e.g.) drop-on-unwinding-panic to happen (as a pragmatic loophole to avoid breaking too much code), but then the const assert probably won't work either (because the compiler will not attempt to prove that the panic drop glue is never invoked).
[1]: https://doc.rust-lang.org/reference/expressions/block-exp...
Posted Feb 12, 2025 23:02 UTC (Wed)
by khim (subscriber, #9252)
[Link] (3 responses)
C++17 solved that with the if constexpr that have such guarantees. Why is it not a problem with C++, then? No. There are no need for that. Unlike traits resolution And yes, C++ have a rule that if something couldn't be instantiated then it's not a compile-time error if that instantiation is not needed. That included destructors. It was there since day one, that is, from year C++98 and while MSVC was notoriously bad at following that rule clang was always pretty precise. If some instantiations of that To the large degree it's chicken-end-egg issue: C++ have, essentially, build all it's advanced techniques around SFINAE and thus compilers learned to handle it well, in Rust very few developer even know or care that it's analogue exist in a language, thus it's not handled correctly in many cases. But no, it's not matter of complicated math or something that should slow down the compilation, on the contrary that's something that's relatively easy to implement: C++ is in existential proof. Yes, but, ironically enough, that would require a lot of work because if you do that then you are elevating the whole thing to the level of types, errors are detected pre-monomorphisation and now it's not longer an issue of implementing things carefully but it becomes a typesystem problem.
Posted Feb 13, 2025 0:14 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link] (2 responses)
It has to be in the type system in some form, or else generic code cannot coherently interact with it (unless we want to throw away literally all code that drops anything and start over with a system where drop glue is not a thing - but that's so obviously a non-starter that I cannot imagine you could be seriously proposing it). We do not want to recreate the C++ situation where all the types match, but then something blows up in monomorphization.
To be more concrete: Currently, in Rust, you can (mostly) figure out whether a given generic specialization is valid by reading its trait bounds. While it is possible to write a const block that imposes additional constraints, this is usually used for things like FFI and other cases where the type system doesn't "know" enough to stop us from doing something dangerous. Outside of those special cases (which typically make heavy use of unsafe), the general expectation is that if a type matches a given set of trait bounds, then I can use it as such.
This is useful for pedagogical reasons, but it is also just a heck of a lot more convenient. When I'm writing concrete code, I don't have to grep for std::static_assert or read compiler errors to figure out what methods I'm allowed to call. I can just read the trait bounds in the rustdoc and match them against my concrete types. When I'm writing generic code, I don't have to instantiate a bunch of specializations to try and shake out compiler errors one type at a time. The compiler will check the bounds for me and error out if I write something incoherent, even before I write any tests for my code.
But trait bounds are more than just a convenient linting service. They are a semver promise. If I have a function foo<T: Send>(t: T), I am not allowed to change it to foo<T: Send + Sync>(t: T) without breaking backwards compatibility. If you wrote foo::<RefCell>(cell) somewhere in your code, I have promised that that will continue to work in future releases, even if I never specifically thought about RefCell. Droppability completely breaks this premise. If droppability is only determined at monomorphization, then I can write a function bar<T>(t: T) -> Wrapper<T> (for some kind of Wrapper type) that does not drop its argument, and then later release a new version that has one unusual code path where the argument does get dropped (by one of its transitive dependencies, just to make the whole thing harder to troubleshoot). Under Rust as it currently exists, that is not a compatibility break, and it would be very bad if it was. We would have to audit every change to every generic function for new drop glue, or else risk breaking users of undroppable types. Nobody is actually going to do that, so we're simply not going to be semver compliant in this hypothetical.
Posted Feb 13, 2025 8:00 UTC (Thu)
by khim (subscriber, #9252)
[Link] (1 responses)
Well… it's like Greenspun's tenth rule. Rust didn't want to “recreate the C++ situation” and as a result it just made bad, non-functional copy. As you saw “something blows up in monomorphization” is already possible, only it's unreliable, had bad diagnosis and in all aspects worse that C++. Perfect is enemy of good and this story is great illustration IMNSHO. Yeah. A great/awful property which works nicely, most of the time, but falls to pieces when you really try to push. It's convenient till you need to write 566 methods instead of 12. At this point it becomes both a PITA and compilation times skyrocket. But when these bounds are not there from the beginning they are becoming more of “semver rejection”. That's why we still have no support for lending iterator in At some point you have to accept that your language couldn't give you perfect solution and can only give you good one. Rust developers haven't accepted that yet and thus we don't have solutions for many issues at all. And when choice is between “that's impossible” and “that's possible, but with caveats” practical people pick the latter option. Except, as you have already demonstrated, that's not true, this capability already exist in Rust – and “perfect solutions” don't exist… after 10 years of development. Maybe it time to accept the fact that “perfect solutions” are not always feasible. That's already a reality, Rust is already like this. It's time to just accept that.
Posted Feb 13, 2025 18:19 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link]
This is exactly the same attitude that everyone had towards borrow checking before Rust existed.
Sure, *eventually* you probably do have to give up and stop trying to Change The World - a language has to be finished at some point. But Rust is very obviously not there yet. It would be a shame if they gave up on having nice, well-behaved abstractions just because some of the theory is inconveniently complicated. Traits and bounds are improving, slowly, along with most of the rest of the language. For example, the standard library already makes some use of specialization[1][2], a sorely missing feature that is currently in the process of being stabilized.
Rust is not saying "that's impossible." They're saying "we want to take the time to try and do that right." I say, let them. Even if they fail, we can learn from that failure and adapt. But if you never try, you can never succeed.
[1]: https://rust-lang.github.io/rfcs/1210-impl-specialization.html
Ada does this too
> It does not promise that an unreachable const block is never evaluated (in the general case, that would require solving the halting problem).
Ada does this too
ManualDrop
and maybe some unsafe
.Ada does this too
Ada does this too
replace
and put it into your crate without telling the compiler that it needs to do some special dance then everything works.unsafe
code and standard library – which would, essentially, turn it into a different language.static_assert
-based linear types work are not two different kinds of work, but, in fact, exactly the same work.Ada does this too
// The panic may or may not occur when the program is built.
const { panic!(); }
}
> Currently, the reference[1] says that this is allowed to fail at compile time:
Ada does this too
const
evaluation happens after monomorphisation, not before. It's exactly like C++ templates and should work in similar fashion and require similar resources. Templates in C++ were handled decently by EDG 30 years ago and computers were much slower back then.static_assert
in linear type destructor is happening when they shouldn't happen it's matter to tightening the specifications and implementations, not question of doing lots of math and theorem-proving.Ada does this too
> We do not want to recreate the C++ situation where all the types match, but then something blows up in monomorphization.
Ada does this too
for
. And said lending iterator was a showcase of GATs four years ago.Ada does this too
[2]: https://doc.rust-lang.org/src/core/iter/traits/iterator.r...