HN user

Rusky

4,445 karma
Posts39
Comments1,296
View on HN
travisdowns.github.io 2y ago

Where Do Interrupts Happen? (2019)

Rusky
1pts1
www.youtube.com 2y ago

Modernizing compiler design for Carbon's toolchain [video]

Rusky
67pts32
discourse.llvm.org 4y ago

Lifetime Annotations for C++

Rusky
33pts0
www.blackhat.com 5y ago

Finding Bugs Compiler Knows but Doesn't Tell You: Undefined Behavior in LLVM

Rusky
2pts0
www.abubalay.com 5y ago

The problem of safe FFI bindings in Rust

Rusky
2pts0
www.abubalay.com 7y ago

Soak: a struct-of-arrays library in Rust

Rusky
2pts0
www.abubalay.com 8y ago

Writing a recursive ascent parser by hand

Rusky
140pts42
godotengine.org 8y ago

Godot 3.0 game engine released

Rusky
308pts39
mattgathu.github.io 8y ago

Writing a Command Line Tool in Rust

Rusky
3pts0
users.rust-lang.org 8y ago

Try Out Rust IDE Support in Visual Studio Code

Rusky
265pts82
www.ncameron.org 8y ago

What the Rust Language Server Can Do

Rusky
4pts0
millcomputing.com 9y ago

Switches on the Mill CPU

Rusky
4pts0
www.ralfj.de 9y ago

How MutexGuard Was Sync When It Should Not Have Been

Rusky
78pts8
smallcultfollowing.com 9y ago

Query structure in chalk

Rusky
2pts0
blogs.msdn.microsoft.com 9y ago

Introducing PIX on Windows (beta)

Rusky
2pts0
ticki.github.io 9y ago

Why Rust's `std::collections` is absolutely fantastic

Rusky
4pts0
davazp.net 9y ago

Eulex – Bare-metal x86 FORTH

Rusky
3pts0
smallcultfollowing.com 10y ago

Rust Parallel Iterators Part 2: Producers

Rusky
1pts0
popehat.com 10y ago

A Few Comments on the UN's “Cyber Violence Against Women and Girls” Report

Rusky
3pts0
millcomputing.com 11y ago

The Mill CPU's Compiler [video]

Rusky
4pts0
lwn.net 11y ago

Parallel page rendering with Mozilla Servo

Rusky
2pts0
smallcultfollowing.com 11y ago

On Reference Counting and Leaks in Rust

Rusky
7pts0
ptgmedia.pearsoncmg.com 11y ago

How to ensure that your program does not run under Windows 95 [pdf]

Rusky
48pts20
millcomputing.com 12y ago

Mill CPU Specification [video]

Rusky
103pts32
www.reddit.com 12y ago

Why a 4 core 3.5 GHz Intel i5 CPU beats an 8 core 4.7 GHz AMD CPU

Rusky
6pts0
llvm.lyngvig.org 12y ago

Mapping High Level Constructs to LLVM IR

Rusky
2pts0
elm-lang.org 14y ago

Elm: a functional reactive programming language that compiles to JavaScript

Rusky
132pts50
www.smbc-comics.com 14y ago

It cuts both ways.

Rusky
2pts0
journal.stuffwithstuff.com 14y ago

Semicolons are a Shibboleth

Rusky
3pts0
www.team.net 15y ago

Unix - The Hole Hawg

Rusky
4pts0
AI is a bad tool 9 days ago

Compilers don't take away the code we need to write; they translate it into a different formal language that emphasizes and de-emphasizes various aspects of its meaning.

LLMs are categorically a different thing. Instead of soundly translating between formal languages, they adjust how you interact with the formal language.

The enjoyment people get from coding has absolutely nothing to do with the pure volume of code they produce, to the point that this has long been a cliche!

The types are there for garbage collection, which is there for integration with the Web APIs which are all defined in terms of garbage collected objects.

The thing is that anyone can show up and spend time discussing ideas in Rust project spaces. From the outside it is easy to confuse that with actual movement toward landing changes in the language.

(The communication aspect of this is something that has bothered me many times in the past- even people who are lang team members often phrase things in a way that makes it sound like something is on its way in, when it's still just in the stage of "we're kinda noodling with ideas.")

Yoshua is part of the "loud contingent" being described. He's not on the lang team, and he's been "working on" things like keyword generics for years without any indication that they are going to make it into the language.

Or put another way, a hypothetical feature that you made up in your head is the thing that requires source access. Editions do not let you change the semantics of types.

To be fair, Rust tooling does tend toward build-from-source. But this is for completely different reasons than the edition system: if you had a way to build a crate and then feed the binary into builds by future compilers, it would require zero additional work to link it into a crate using a different edition.

Not at all. It's much more efficient to implement a GC on x86 or ARM than it is on Wasm 1.0/2.0, because you control the stack layout, and you don't have an impenetrable security boundary with the JS runtime that your GC needs to interop with.

Not to mention the issue that bundling a GC implementation as part of your web page can be prohibitive in terms of download size.

The reason rust-analyzer doesn't update diagnostics until you save is historical. Originally, people tried to build IDE support by reusing rustc itself, but this proved too slow and cumbersome at the time.

Rust-analyzer reimplemented the frontend in a more IDE-friendly architecture, but focused more on name resolution than on type checking. So it delegated diagnostics to literally just running `cargo check`.

As parts of rustc get rewritten over time (the trait solver, borrow checker) they have also been made more IDE-friendly and reusable, so rust-analyzer is slowly gaining the ability to surface more type checking diagnostics as you edit, without delegating to `cargo check`.

TCO is less of an optimization (which are typically best-effort on the part of the compiler) and more of an actual semantic change that expands the set of valid programs. It's like a new control flow construct that lives alongside `while` loops.

Giving special treatment to code that "explicitly wants" to handle unwinding means two things:

* You have to know when an API can unwind, and you have to make it an error to unwind when the caller isn't expecting it. If this is done statically, you are getting into effect annotation territory. If this is done dynamically, are essentially just injecting drop bombs into code that doesn't expect unwinding. Either way, you are multiplying complexity for generic code. (Not to mention you have to invent a whole new set of idioms for panic-free code.)

* You still have to be able to clean up the resources held by a caller that does expect unwinding. So all your vocabulary/glue/library code (the stuff that can't just assume panic=abort) still needs these "scoped panic hooks" in all the same places it has any level of panic awareness in Drop today.

So for anyone to actually benefit from this, they have to be writing panic-free code with whatever new static or dynamic tools come with this, and they have to be narrowly scoped and purpose-specific enough that they could essentially already today afford panic=abort. Who is this even for?

Suppose, instead, we had a mechanism that allowed registering arbitrary panic hooks, and unregistering them when no longer needed, in any order. Then, we could do RAII-style resource handling: you could have a `CursesTerminal` type, which is responsible for cleaning up the terminal, and it cleans up the terminal on `Drop` and on panic. To do the latter, it would register a panic hook, and deregister that hook on `Drop`.

This doesn't get rid of unwinding at all- it's an inefficient reimplementation of it. There's a reason language implementations have switched away from having the main execution path register and unregister destructors and finally blocks, to storing them in a side table and recovering them at the time of the throw.

You don't need any of that, and you can keep cancellation too.

The core of an eager cooperative multitasking system does not even need the concept of an executor. You can spawn a new task by giving it some stack space and running its body to its first suspension point, right there on the current thread. When it suspends, the leaf API (e.g. `lock`) grabs the current top of the stack and stashes it somewhere, and when it's time to resume it again just runs the next part of the task right there on the current thread.

You can build different kinds of schedulers on top of this first-class ability to resume a particular leaf call in a task. For example, a `lock` integrated with a particular scheduler might queue up the resume somewhere instead of invoking it immediately. Or, a generic `lock` might be wrapped with an adapter that re-suspends and queues that up. None of this requires that the language know anything about the scheduler at all.

This is all typical of how higher level languages implement both stackful and stackless coroutines. The difference is that we want control over the "give it some stack space" part- we want the compiler to compute a maximum size and have us specify where to store it, whether that's on the heap (e.g. tokio::spawn) or nested in some other task's stack (e.g. join, select) or some statically-allocated storage (e.g. on a microcontroller).

(Of course the question then becomes, how do you ensure `lock` can't resume the task after it's been freed, either due to normal resumption or cancellation? Rust answers this with `Waker`, but this conflates the unit of stack ownership with the unit of scheduling, and in the process enables intermediate futures to route a given wakeup incorrectly. These must be decoupled so that `lock` can hold onto both the overall stack and the exact leaf suspension point it will eventually resume.)

Cancellation doesn't change much here. Given a task held from the "caller end" (as opposed to the leaf callee resume handles above), the language needs to provide a way to destruct the stack and let the decoupled `Waker` mechanism respond. This still propagates naturally to nested tasks like join/select arms, though there is now an additional wrinkle that a nested task may be actively running (and may even be the thing that indirectly provoked the cancellation).

in principle the exact same optimization could be done for stackful coroutines.

Yes, I totally agree, and this is sort of what I imagine a better design would look like.

One of the reasons Rust does it the way it currently does is because the implementation avoids requiring support from, e.g., LLVM

This I would argue is simply a failure of imagination. All you need from the LLVM layer is tail calls, and then you can manage the stack layout yourself in essentially the same way Rust manages Future layout.

You don't even need arbitrary tail calls. The compiler can limit itself to the sorts of things LLVM asks for- specific calling convention, matching function signatures, etc. when transferring control between tasks, because it can store most of the state in the stack that it laid out itself.

"Not inert" does not at all imply "a single runtime within std+compiler." You've jumped way too far in the opposite direction there.

The problem is that the particular interface Rust chose for controlling dispatch is not granular enough. When you are doing your own dispatch, you only get access to separate tasks, but for individual futures you are at the mercy of combinators like `select!` or `FuturesUnordered` that only have a narrow view of the system.

A better design would continue to avoid heap allocations and allow you to do your own dispatch, but operate in terms of individual suspended leaf futures. Combinators like `join!`/`select!`/etc. would be implemented more like they are in thread-based systems, waiting for sub-tasks to complete, rather than being responsible for driving them.

The requirement is that the futures are not separate heap allocations, not that they are inert.

It's not at all obvious that Rust's is the only possible design that would work here. I strongly suspect it is not.

In fact, early Rust did some experimentation with exactly the sort of stack layout tricks you would need to approach this differently. For example, see Graydon's post here about the original implementation of iterators, as lightweight coroutines: https://old.reddit.com/r/ProgrammingLanguages/comments/141qm...

If that is what profiles were actually doing, it would probably make sense. But it's not what profiles are doing.

Instead, for example, the lifetime safety profile (https://github.com/isocpp/CppCoreGuidelines/blob/master/docs...) is a Rust-like compile time borrow checker that relies on annotations like [[clang::lifetimebound]], yet they also repeatedly insist that profiles will not require this kind of annotation (see the papers linked from https://www.circle-lang.org/draft-profiles.html#abstract).

Their messaging is just not consistent with the concrete proposals they have described, let alone actually implemented.

I'd be fine writing `.into()` or `.trunc()`

Yes, this is specifically what I'm disagreeing with.

I fully expect that such methods will be inlined, likely even in debug mode (e.g. `#[inline(always)]`), and compile down to the same minimal instructions.

That's the cost to compile time I mentioned.

A method call like `.trunc()` is still going to be abysmally less ergonomic than `as`. It relies on inference or turbofish to pick a type, and it has all the syntactic noise of a function call on top of that.

Not to mention this sort of proliferation of micro-calls for what should be <= 1 instruction has a cost to debug performance and/or compile times (though this is something that should be fixed regardless).

The tokenizer is not really a good demonstration of the differences between these styles. A more representative comparison would be the later stages that build, traverse, and manipulate tree and graph data structures.

"Hand-rolled assembly" was one item in a list that also included DoD. You're reading way more into that sentence than they wrote- the claim is that DoD itself also impacts the maintainability of the codebase.

What a thought-terminating way to approach an idea. Effects are not simply renamed conditions, and we have a whole article here describing them in more detail than that one sentence, so you can see some of the differences for yourself.