7,581 karma · joined September 4, 2012
I guess cancellation is really two different things, which usually happen at the ~same time, but not in this case: 1) the future stops getting polled, and 2) the future gets dropped. In this example the drop is delayed, and because the future is holding a guard,* the delay has side effects. So the future "has been cancelled" in the sense that it will never again make forward progress, but it "hasn't been cancelled yet" in the sense that it's still holding resources. I wonder if it's practical to say "make sure those two things always happen together"?
* Technically a Tokio-internal `Acquire` future that owns a queue position to get a guard, but it sounds like the exact same bug could manifest after it got the guard too, so let's call it a guard.
Edit: Oh it looks like another option, `KeccakSum`, was released a couple months ago? https://github.com/XKCP/K12/commit/5271b58c990c1ac33c1097b4e...
Which is all kind of to say, all of Rust's different safety rules end up being surprisingly densely interconnected. It's really hard to guarantee one of them (say "no use-after-free") without ultimately requiring all of them ("no uninitialized variables", "no mutable aliasing", etc).
This jives with a point that the Google Security Blog made last year: "The [memory safety] problem is overwhelmingly with new code...Code matures and gets safer with time."
https://security.googleblog.com/2024/09/eliminating-memory-s...
I wouldn't put it that way. Usually when we say the compiler is "incorrect", we mean that it's generating code that breaks the observable behavior of some program. In that sense, adding extra checks that can't actually fail isn't a correctness issue; it's just an efficiency issue. I'd usually say the compiler is being "conservative" or "defensive". However, the "100% branch testing" strategy that we're talking about makes this more complicated, because this branch-that's-never-taken actually is observable, not to the program itself but to its test suite.
function xor(x, y) {
return (x + y - 2 \* and(x, y));
}
TIL!It doesn't seem bonkers to me. I know you already know these details, but spelling it out: If I'm using select/poll/epoll in C to do non-blocking reads of a socket, then yes I can use any old stack buffer to receive the bytes, because those are readiness APIs that only write through my pointer "now or never". But if I'm using IOCP/io_uring, I have to be careful not to use a stack buffer that doesn't outlive the whole IO loop, because those are completion APIs that write through my pointer "later". This isn't just a question of the borrow checker being smart enough to analyze our code; it's a genuine difference in what correct code needs to do in these two different settings. So if async Rust forces us to use heap allocated (or long-lived in some other way) buffers to do IOCP/io_uring reads, is that a failure of the async model, or is that just the nature of systems programming?
The expensive thing: 100 per week -> 200 per week
This is...the opposite of a nerf? The numbers goed up? (We can quibble about the daily vs hourly difference, but certainly for me the weekly cap was the only thing that mattered.)
I think there's another part of the story in a few of these examples that the author might be missing. Sometimes Rust doesn't want to infer/allow things because of the Halting Problem or whatever, but other times it's because that inference would amount to a compatibility hazard that the user might not've realized they were creating. "Types implement `Copy` automatically" is a good example of one of these. It would be an easy rule to implement (and it is in fact how `Send` and `Sync` and some other things work), but it would also mean that adding a non-Copy private field to a struct that didn't previously have one could break callers. I wouldn't call it a slam-dunk case for the rules being what they are -- and you could argue that it prioritizes the needs of library code over the needs of application code -- but anyway it's some context.
> I spoke to Apple folks when their compiler team switched the default to strict aliasing. They reported that it made key workloads 5-10% faster and the fixes were much easier to do and upstream than I would have expected. My view of -fstrict-aliasing at the time was that it was a flag that let you generate incorrect code that ran slightly faster. They had actual data that convinced me otherwise.
fn foo() {
let mut x = 42;
let mut mutable_references = Vec::new();
let test: bool = rand::random();
if test {
mutable_references.push(&mut x);
} else {
mutable_references.push(&mut x);
}
}
Because only one if/else branch is ever allowed to execute, the compiler can see "lexically" that only one mutable reference to `x` is created, and `foo` compiles. But this other function that's "obviously" equivalent doesn't compile: fn bar() {
let mut x = 42;
let mut mutable_references = Vec::new();
let test: bool = rand::random();
if test {
mutable_references.push(&mut x);
}
if !test {
mutable_references.push(&mut x); // error: cannot borrow `x` as mutable more than once at a time
}
}
The Rust compiler doesn't do the analysis necessary to see that only one of those branches can execute, so it conservatively assumes that both of them can, and it refuses to compile `bar`. To do things like `bar`, you have to either refactor them to look more like `foo`, or else you have to use `unsafe` code.> Given that aliasing optimizations are something that the Rust compiler developers clearly want to support, we need some way of “ruling out” counterexamples like the one above from consideration.
The `requires-version` field in `pyproject.toml` defines a range of compatible versions, while `.python-version` defines the specific version you want to use for development. If you create a new project with uv init, they'll look similar (>=3.13 and 3.13 today), but over time `requires-version` usually lags behind `.python-version` and defines the minimum supported Python version for the project. `requires-version` also winds up in your package metadata and can affect your callers' dependency resolution, for example if your published v1 supports Python 3.[old] but your v2 does not.
I get where this is coming from, and I even agree with it today, but I also want to tag it as "don't cache this opinion too hard". It's interesting to notice when and how our advice for getting help from AI is different from our advice for getting help from other humans. It would be kind of odd, wouldn't it, to put a paragraph at the bottom of a blog post that said "by the way if you have friends who are expert programmers, I don't recommend asking them for help." I think there are two clear reasons that feels odd: 1) expert friends can actually answer your questions and get you personally unstuck, which is huge, and 2) expert friends usually understand why you're doing what you're doing and that they're supposed to help do it yourself instead of just doing it for you.
One thing I bet few people have tried (because I haven't tried it myself) is actually asking an LLM to guide you like an expert friend would, instead of just spitting out code to solve your problem. Maybe they're bad at that, I wouldn't be surprised. But if so, I bet in a year or two they'll be amazing at it. It might be good to build the habit of clarifying what sort of help you need, instead of assuming an LLM will give you the wrong kind of help?
If the calculator has a little gremlin in it that rolls a random 100-sided die, and gives you the wrong answer every time it rolls a 1, then you certainly can use it to build a bridge. You just need to do each calculation say 10 or 20 times and take the majority answer :)
If the gremlin is clever, it might remember the wrong answers it gave you, and then it might give them to you again if you ask about the same numbers. In that case you might need to buy 10 or 20 calculators that all have different gremlins in them, but otherwise the process is the same.
Of course if all your gremlins consistently lie for certain inputs, you might need to do a lot of work to sample all over your input space and see exactly what sorts of numbers they don't like. Then you can breed a new generation of gremlins that...