Posted by ravenical 9 hours ago
What I _do_ find annoying though and I cannot wrap my head around are lifetimes. Every time I think I understand it, I end up getting it wrong.
Evidently millions of people want broken garbage, Herb Sutter even wrote a piece celebrating how many more C++ programmers and projects there were last year, churning out yet more broken garbage, it's a metaphor for 2025 I guess.
But Rust, its community, and language flame wars are separate concerns. When I talk shop with other Rust people, we talk about our projects, not about hating C++.
And although of course things could have been better they could also have been worse. C++ drinks too much OO kool aid, but hey it introduced lots of people to generic programming which is good.
The number of such domains has gone down over time, and will probably continue to do so.
Hence, they use GC'd languages like Go whenever they can.
Haskell (and OCaml etc) give you both straightjackets and a garbage collector. Straightjackets and GC are very compatible.
Compared to C, which has neither straightjackets nor a GC (at least not by default).
Haskell's thing with purity and IO does not feel like that. In fact Haskell does it right (IO type is reflected in type). And rust messed it up ("safety" does not show up in types).
You want a global mutable thing in Haskell? just use something like an `IORef` and that is it. It does not involve any complicated type magic. But mutations to it will only happen in IO, and thus will be reflected in types. That is how you do it. That is how it does not feel like a straight jacket.
Haskell as a language is tiny. But Rust is really huge, with endless behavior and expectation to keep in mind, for some some idea of safety that only matter for a small fraction of the programs.
And that I why I find that comment very funny. Always using rust is like always wearing something that constrains you greatly for some idea of "safety" even when it does not really matter. That is insane..
It does in rust. An `unsafe fn()` is a different type than a (implicitly safe by the lack of keyword) `fn()`.
The difference is that unsafe fn's can be encapsulated in safe wrappers, where as IO functions sort of fundamentally can't be encapsulated in non-IO wrappers. This makes the IO tagged type signatures viral throughout your program (and as a result annoying), while the safety tagged type signatures are things you only have to think about if you're touching the non-encapsulated unsafe code yourself.
This is the koolaid I am not willing to drink.
If you can add safety very carefully on top of unsafe stuff (without any help from compiler), why not just use `c` and add safety by just being very careful?
> IO tagged type signatures viral throughout your program (and as a result annoying)..
Well, that is what good type systems do. Carry information about the types "virally". Anything short is a flawed system.
> If you can add safety very carefully on top of unsafe stuff (without any help from compiler), why not just use `c` and add safety by just being very careful?
There is help from the compiler - the compiler lets the safe code expose an interface that creates strict requirements about how it is being called with and interacted with. The C language isn't expressive enough to define the same safe interface and have the compiler check it.
You can absolutely write the unsafe part in C. Rust is as good at encapsulating C into a safe rust interface as it is at encapsulating unsafe-rust into a safe rust interface. Just about every non-embedded rust program depends on C code encapsulated in this manner.
> Well, that is what good type systems do. Carry information about the types "virally". Anything short is a flawed system.
Good type systems describe the interface, not every implementation detail. Virality is the consequence of implementation details showing up in the interface.
Good type systems minimize the amount of work needed to use them.
IO is arguably part of the interface, but without further description of what IO it's a pretty useless detail of the interface. Meanwhile exposing a viral detail like this as part of the type system results in lots of work. It's a tradeoff that I think is generally not worth it.
The compiler does not and cannot check if these strict requirements are enough for the intended "safety". Right? It is the judgement of the programmer.
And what is stopping a `c` function with such requirements to be wrapped in some code that actually checks these requirements are met? The only thing that the rust compiler enables is to include a feature to mark a specific function as unsafe.
In both cases there is zero help from the compiler to actually verify that the checks that are done on top are sufficient.
And if you want to mark a `c` function as unsafe, just follow some naming convention...
>but without further description of what IO it's a pretty useless detail of the interface..
Take a look at effect-system libraries which can actually encode "What IO" at the type level and make it available everywhere. It is a pretty basic and widely used thing.
It might be because Monads could have a tad bit advanced type machinery. But IORefs are straightforward, but typically one does not come across it until a bit too late into their Haskell journey.
Only if you’re insane.
Just don't accidentally step on any of these landmines and we'll all get along great.
Rust says that all incorrect programs (in terms of memory safety) are invalid but the trade is that some correct programs will also be marked as invalid because the compiler can't prove them correct.
C++ says that all correct programs are valid but the trade is that some incorrect programs are also valid.
You see the same trade being made with various type systems and people still debate about it but ultimately accept that they're both valid and not garbage.
C++ does not say this, in fact no statically typed programming language says this, they all reject programs that could in principle be correct but get rejected because of some property of the type system.
You are trying to present a false dichotomy that simply does not exist and ignoring the many nuances and trade-offs that exist among these (and other) languages.
It works in the sense that the borrow checker stops bothering you and the compiler will compile your code. It will even work fine as long as you don't write code which invokes UB (which does include code which would not pass the borrow checker, as the borrow checker necessarily rejects valid programs in order to forbid all invalid programs).
To be clear, by "this" I meant "[allowing] code that would normally violate Rust's borrowing rules to compile and run successfully," which both of us seem to believe to be UB.
That is part of why a number of people have been waiting for Polonius and / or the tree borrows model, most classic are relatively trivial cases of "check then update" which fail to borrow check but are obviously non-problematic e.g.
pub fn get_or_insert (
map: &'_ mut HashMap<u32, String>,
) -> &'_ String
{
if let Some(v) = map.get(&22) {
return v;
}
map.insert(22, String::from("hi"));
&map[&22]
}
Though ultimately even if either or both efforts bear fruits they will still reject programs which are well formed: that is the halting problem, a compiler can either reject all invalid programs or accept all valid programs, but it can not do both, and the former is generally considered more valuable, so in order to reject all invalid programs compilers will necessarily reject some valid programs.In the following example, z is dereferenced one time and assigned to both x and y, but if z and x are aliased, then this is an invalid optimization.
fn increment_by(x: &mut i32, y: &mut i32, z: &i32) {
*x = *z;
*y = *z;
}
https://rust.godbolt.org/z/Mc6fvTzPGThis is a binary assumption that you can understand to evaluate to "true" in the absence of a borrow checker. If it is "false" it halts the compiler
If Rust were to "borrow" something from the C/C++ spirit, then disabling the borrow checker should be available as a compiler option.
As in, you're an adult: if you want it, you can have it, instead of "we know better".
It doesn't make much sense to globally relax restrictions of Rust's references to be like C/C++ pointers, because the reference types imply a set of guarantees: must be non-null (affects struct layout), always initialized, and have strict shared/immutable vs exclusive access distinction. If you relax these guarantees, you'll break existing code that relies on having them, and make the `--yolo` flag code incompatible with the rest. OTOH if you don't remove them, then you still have almost all of borrow checker's restrictions with none of the help of upholding them. It'd be like a flag that disables the sign bit of signed integers. It just makes an existing type mean something else.
You can already disable it locally: the unsafe keyword is for that.
You don't technically. The borrow checker doesn't effect the semantics of the program (like, for example, type inference does) and the rest of the compiler doesn't need to (and in fact, doesn't) use its analysis to figure out how to compile the code.
The downstream compiler does assume that the code followed the rules for accessing references - i.e. didn't violating aliasing rules. The borrow checker guarantees this, but it's fundamentally a conservative check. It rejects programs it can't guarantee are correct, and rice's theorem proves that there are always correct programs that it can't guarantee are correct.
That said if you just treat rust-references like C-pointers you will run into issues. The aliasing rules for rust references are stricter. Also not fully agreed upon yet - the currently closest to accepted definition is in the "tree borrows" paper but it has yet to be adopted as the official one by the rust team.
If we can't have this, C itself offers zero benefit over assembly.
Because it's fun.
I can totally understand why you wouldn't want to do this though - the plethora of incompatible lisp dialects come to mind. That's why I said it was controversial.
Where the vectorized function checks if the chunk has length 32, if yes run the algorithm, else run the algorithm.
The compiler knows that the chunk has a fixed size at compile time in the first block, which means it can now attempt to vectorize the algorithm with a SIMD size of 32. The else block handles the scalar case, where the chunk is smaller than 32.
The borrow checker does not influence codegen at all.
It's actually interesting to me that the Rust borrow checker can 'simply' be disabled (e.g. no language- or stdlib-features really depending on the borrow checker pass) - not that it's very useful in practice though.
> In addition to meeting the Open Source Definition, the following standards apply to new licenses:
> (...) The license does not have terms that structurally put the licensor in a more favored position than any licensee.
https://opensource.org/licenses/review-process
That's a funfact I learned from IP lawyer when discussing possibility of open-source but otherwise LLVM-extempt license. If there is extemption (even in LLM) such license is most likely OSI-incompatible.For those of you confused: yes, this started as a satirical project with the corroded lib. Then I thought "why not just remove the borrow checker?" without any real motivation. Then I just went ahead and did it. To my surprise, it was really simple and easy. I thought it would be heavily tangled into the rustc compiler, but once I figured out where the error emitter is, it was pretty straightforward.
I'm not sure about my long-term goals, but besides the joke, I genuinely think for debugging and prototyping purposes, I'd like the borrow checker to shut up. I'm the kind of guy that prints everything while debugging and prototyping. Maybe you're using a debugger, okay, but I don't. I don't like debuggers. It's just more convenient for me. So what constantly happens is I run into issues like: does this implement Debug? Can I print this after it moved? The borrow checker won't let me access this because of some other borrow. Stuff like that.
Another point is, as you guys are well aware, the borrow checker will reject some valid programs in order to never pass any invalid program. What if I'm sure about what I'm doing and I don't want that check to run?
In the repo there's a doubly linked list example. Without the borrow checker it's fairly simple and easy to implement. With it, you know how complicated and ugly it gets.
Anyway, have a good new year, and don't get angry over compilers, you know.
Reminds me of a chemistry kit I had as a kid. None of this tame, safe stuff you can buy these days. Mine was a gift from my dad and I never thought of asking him where he dug it up, but it had stuff like pure sulfuric acid in it.
One day, when I was done with all of the experiments I had planned to do, I decided to mix a few things and heat them up, just for fun, without any real motivation other than "let's see what happens".
Let's just say I was lucky we only had to replace some of the clothes my mom had left out for me to put away. ;)
> Another point is, as you guys are well aware, the borrow checker will reject some valid programs in order to never pass any invalid program. What if I'm sure about what I'm doing and I don't want that check to run?
Then you do it using the "unsafe" keyword, and you think long and hard about how to design and structure the code so that the unsafe code is small in scope, surface, and blast radius.
That's precisely what unsafe code is for: to get around the borrow checker and assert you know what you're doing. Of course, if you're wrong, that means your program will blow up, but at least you know that the culprit is hiding in one of those unsafe areas, rather than literally anywhere in the whole codebase.
Alternately, you can switch to a language with a different ethos.
The ethos of Rust is caring for memory safety so much that you willingly limit yourself in terms of what kind of code you write and you only step out of those limits reluctantly and with great care. That's something that resonates with a lot of people and Rust has been built on top of that for years.
If you suddenly take the product of those years of hard work, strip out the foundation it has been built on, and unironically offer it as a good idea, a lot of people won't like it and will tell you so. Mind, I'm not excusing the personal attacks, I'm just explaining the reaction.
I gotta applaud that level of my-way-or-the-highway