HNHacker News
TopNewBestAskShowJobs

Rusky

4,446 karma · joined November 23, 2009

submissionscomments
Rusky··on Why Algebraic Effects?
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.
Rusky··on Why Algebraic Effects?
The handler doesn't have to follow the pattern of "do its work, resume the computation, go away."

It can instead do things like "do some work, resume the computation, do some more work."

Or even more invasively, "stash the computation somewhere, return from the handler site, let the rest of the program run for a while, then resume the computation."

Rusky··on How (memory) safe is Zig? (2021)
Yeah, you're still responding to something I'm not saying, and not saying anything I'm trying to argue with.

I wrote "who" as shorthand for "the language implementation vs the individual programs."

Rusky··on How (memory) safe is Zig? (2021)
I mean, again, yeah. I specifically compared the safe API/unsafe implementation aspect, not who writes the unsafe implementation.

To me the interesting thing about Rust's approach is precisely this ability to compose unrelated pieces of trusted code. The type system and dynamic semantics are set up so that things don't just devolve into a yolo-C-style free-for-all when you combine two internally-unsafe APIs: if they are safe independently, they are automatically safe together as well.

The set of internally-unsafe APIs you choose to compose is a separate question on top of that. Maybe Rust, or its ecosystem, or its users, are too lax about this, but I'm not really trying to have that argument. Like I mentioned in my initial comment, I find this interesting even if you just apply it within a single trusted runtime.

Rusky··on How (memory) safe is Zig? (2021)
Yeah, that's what I meant by "depends on the specific programs you're writing." Confining unsafe Rust to core libraries is totally something people do.
Rusky··on How (memory) safe is Zig? (2021)
The stuff Fil-C adds is on the same footing as `unsafe` code in Rust- its implementation isn't checked, but its surface area is designed so that (if the implementation is correct) the rest of the program can't break it.

Whether the amount and quality of this kind of code is comparable between the two approaches depends on the specific programs you're writing. Static checking, which can also be applied in more fine-grained ways to parts of the runtime (or its moral equivalent) is an interesting approach, depending on your goals.

Rusky··on Odin: A programming language made for me
Why would it require constructors? As opposed to simply enforcing that it always be initialized with a constant/variable/procedure/etc rather than zeroed.
Rusky··on Odin: A programming language made for me
If you don't want to make it "opt-in" would it at least make sense to make it "opt-out"? Does Odin have a way for specific types to omit a zero value?
Rusky··on Exploiting Undefined Behavior in C/C++ Programs: The Performance Impact [pdf]
It's worth noting (and the paper does go into this) that this is limited to a very specific subset of UB, which they call "guardable."

They are not removing UB around things like out-of-bounds or use-after-free, which would likely be more expensive.

Rusky··on Four Years of Jai (2024)
wavemode's comment only applies to `&mut T`. You do not have to use `&mut T` to form the reference graph in your arena, which indeed would be unlikely to work out.
Rusky··on Four Years of Jai (2024)
That is true of `&mut T`, but `&mut T` is not the only way to do mutation in Rust. The set of possible safe patterns gets much wider when you include `&Cell<T>`. For example see this language that uses its equivalent of `&Cell<T>` as the primary mutable reference type, and uses its equivalent of `&mut T` more sparingly: https://antelang.org/blog/safe_shared_mutability/
Rusky··on Four Years of Jai (2024)
You can explain this sort of pattern to the borrow checker quite trivially: slap a single `'arena` lifetime on all the references that point to something in that arena. This pattern is used all over the place, including rustc itself.

(To be clear I agree that this is an easy pattern to write correctly without a borrow checker as well. It's just not a good example of something that's any harder to do in Rust, either.)

Rusky··on Rust Any part 3: we have upcasts
> And in Rust? Well, let's see. Turns out monomorphization isn't all you need. Turns out that it is, in fact, occasionally useful to unify an object and its behavior in a runtime-polymorphic way. I expect yeet_expr to go through eventually too. Others are trying to stabilize (i.e. ossify) the notionally unstable ABI, just like they did to poor C++, which is now stuck with runtime pessimization because somebody is too lazy to recompile a binary from 2010.

Not to make an argument either way on your general point, but these are really bad examples for Rust if you look at the specifics:

Monomorphization was never the only option. Trait objects and vtables predate the RFC process itself. Early Rust wanted more dynamic dispatch than it eventually wound up with.

The idea of a "throw"-like operator was introduced at the same time as the `?` operator and `try` blocks: https://rust-lang.github.io/rfcs/0243-trait-based-exception-... (Okay, technically `?` was proposed one month previously.)

All the various initiatives related to stable ABI are focused on opt-in mechanisms that work like `#[repr(C)]` and `extern "C"`.

The only way to interpret these as examples of "brash young project walks back its early stridency as it ages" is if you ignore the project's actual reasoning and design choices in favor of the popular lowest-common-denominator Reddit-comment-level understanding of those choices.

Rusky··on Rust Any part 3: we have upcasts
It means you can't just write `impl Bar for MyType` and get Foo pulled in automatically. You have to write both `impl`s yourself.

The inheritance-like syntax is shorthand for `trait Bar where Self: Foo`, and bounds like this can show up in lots of other places, where they follow the same rules: `MyType` has to impl all of them, and the traits and bounds can have fairly arbitrary (or at least un-tree-like) relationships.

The upcasting thing is a rare exception to the idea that this `Self: Foo` bound is just like any other bound.

Rusky··on You Need Subtyping
Algebraic subtyping and structured typing are not the same thing. Algebraic subtyping is a specific approach to type inference and checking of subtyping, which may or may not be used with structural types.
Rusky··on A Guide to Undefined Behavior in C and C++ (2010)
I believe they only standardized the two's-complement representation (so casts to unsigned have a more specific behavior, for example) but they did not make overflow defined.
Rusky··on Effective Rust (2024)
> They can't now. They could up to and almost including 1.0. At that point the consensus was that memory leaks were unsafe and so unsafe code could rely on them not happening. That code was not incorrect!

This is not how it worked, no. It was never memory leaks per se that led to unsoundness there. It was skipping destructors. You could have the exact same unsoundness if you freed the object without running the rest of its destructor first.

That part was the design choice Rust made- make destructors optional and change the scoped threads API, or make destructors required and keep the scoped threads API.

There is an underlying definition of memory safety (or more generally "soundness") that precedes Rust. It is of course defined in terms of a language's "abstract machine," but that doesn't mean Rust has complete freedom to declare any behavior as safe. Memory safety is a particular type of consistency within that abstract machine.

This is why the exact set of undesirable-but-safe operations varies between memory-safe languages. Data races are unsafe in Rust, but they are safe in Java, because Java's abstract machine is defined in such a way that data races cannot lead to values that don't match their types.

Rusky··on Effective Rust (2024)
> Rust's definition of "unsafe" is basically just "whatever Rust prevents".

It's not that circular.

Rust defines data races as unsafe because they can lead to reads that produce corrupt values, outside the set of possibilities defined by their type. It defines memory leaks as safe because they cannot lead to this situation.

That is the yardstick for what makes something safe or unsafe. It is the same yardstick used by other memory-safe languages- for instance, despite your claims to the contrary, garbage collectors do not and cannot guarantee a total lack of garbage. They have a lot of slack to let the garbage build up and then collect it all at once, or in some situations never collect it at all.

There are plenty of undesirable behaviors that fall outside of this definition of unsafety. Memory leaks are simply one example.

Rusky··on Effective Rust (2024)
> - You can't export a reference to the thing you are dropping. You can do that in C++. This prevents "re-animation", where something destroyed comes back to life or is accessed beyond death. Microsoft Managed C++ (early 2000s), supported re-animation and gave it workable semantics. Bad idea, now dead.

>

> - This is part of why Rust destructors cannot run more than once. ...

This is a very backwards way to describe this, I think. Managed C++ only supported re-animation for garbage collected objects, where it is still today a fairly normal thing for a language to support. This is why these "destructors" typically go by a different name, "finalizers." Some languages allow finalizers to run more than once, even concurrently, but this is again due to their GC design and not a natural thing to expect of a "destructor."

The design of Drop and unmanaged C++ destructors is that they are (by default) deterministically executed before the object is deallocated. Often this deallocation is not by `delete` or `free`, which could perhaps in principle be cancelled, but by a function return popping a stack frame, or some larger object being freed, which it simply does not make sense to cancel.

Rusky··on Fixing left and mutual recursions in grammars
That's true as Pratt described it. I mentioned it because it's a good example of the general idea of extending recursive descent to handle more deterministic grammars than vanilla LL.
Rusky··on Fixing left and mutual recursions in grammars
> A properly implemented Earley parser will do unambiguous grammars with left/right/mutual recursion in linear time.

It's not linear for all unambiguous grammars- only deterministic grammars, which can also be parsed with something faster like LR or even hand-written Pratt.

(An example of an unambiguous but nondeterministic grammar is this one for palindromes, which Earley parses in quadratic time: P -> 0 P 0 | 1 P 1 | ε)

Rusky··on Pointers Are Complicated II, or: We need better language specs (2020)
I don't care. I wasn't trying to say whether you were or weren't. Not every comment on HN is a direct rebuttal to its parent.
Rusky··on Pointers Are Complicated II, or: We need better language specs (2020)
> step 2 was fine, because it wasn't C, so it didn't have the aliasing semantics of C.

LLVM still has its own aliasing semantics. It must, both so that it can exploit at least some of C's aliasing rules, and more importantly so that we can determine whether a given transformation is correct, without reference to the entire corpus of optimization passes.

The article describes three possible choices of aliasing semantics for LLVM IR, each of which makes a different optimization pass illegal. You are arguing for one particular choice, without addressing its downsides as also covered in the article.

Rusky··on Pointers Are Complicated II, or: We need better language specs (2020)
My point was that toy examples like this are useful as long as you think about them consistently. I wasn't trying to make any claims about your concrete argument in particular.
Rusky··on Pointers Are Complicated II, or: We need better language specs (2020)
A good mental exercise with these kinds of examples is to imagine splitting up the parts of the program that each optimization looked at, so that they live in different translation units.

This prevents your mind from playing games with what exactly the optimization can know. The article tries to do something similar by fully writing out the output of each optimization as a separate program.

Rusky··on Pointers Are Complicated II, or: We need better language specs (2020)
The IR doesn't have to cater to programmers in the same way, and can support many choices of semantics simultaneously in a single program.

For example, LLVM IR is used to compile C both with and without strict aliasing. The choice is lowered into metadata applied to individual pointer operations. Similarly with signed integer overflow.

Nim could even have gotten some of this benefit when compiling to C if it had been willing to target a specific set of C compilers, rather than standard C.

Rusky··on Fixing left and mutual recursions in grammars
Exactly. This is essentially converting the parser to bottom-up just for these rules. It's how Pratt parsing/precedence climbing works.

I wrote this post on the connection: https://www.abubalay.com/blog/2021/12/31/lr-control-flow

Rusky··on Compiling C to Safe Rust, Formalized
Rust's ownership model has two aspects:

- A dynamic part specifies what is actually allowed, and totally supports doubly linked lists and other sorts of cyclic mutable data structures.

- A static part conservatively approximates the dynamic part, but is still flexible enough to express the interfaces and usage of these data structures even if it can't check their implementations.

This is the important difference over traditional static analysis of C. It enables `unsafe` library code to bridge the dynamic and static rules in a modular way, so that that extensions to the static rules can be composed safely, downstream of their implementation.

Rust's strategy was never for the built-in types like `&mut T`/`&T` to be a complete final answer to safety. It actually started with a lot more built-in tools to support these patterns, and slowly moved them out of the compiler and runtime and into library code, as it turned out their APIs could still be expressed safely with a smaller core.

Something like Fil-C would be totally complementary to this approach- not only could the dynamic checking give you stronger guarantees about the extensions to the static rules, but the static checks could give the compiler more leverage to elide the dynamic checks.

Rusky··on The two factions of C++
Ah, I wasn't really talking about Rust.

Rust already does have some level of const generic expressions, but they are indeed only possible to instantiate with values known at compile time, like C++.

The difficulty of type checking them symbolically still applies regardless of how they're instantiated, but OTOH it doesn't look like Rust is really trying to go that direction.

Rusky··on The two factions of C++
The point of dependent types is to check these uses of N and M at compile time symbolically, for all possible values, without having to "lift" their actual concrete values to compile time.

Typical implementations of dependent types do not generate a separate copy of a function for every instantiation, the way C++ does, so they simply do not need the concrete values in the same way.

← PreviousPage 2 of 34Next →