HNHacker News
TopNewBestAskShowJobs

aw1621107

4,596 karma · joined April 24, 2017

submissionscomments
aw1621107··on When a language's creator and its owners strongly disagree [pdf]
I was in the same boat as you with respect to that paper. I only know its number because Herb mentioned it.

The only red-string-on-a-cork-board I can connect is between guaranteed enforcement and the design decision that "contract assertions must not be used for error handling and input validation". The latter would seem to rule out guaranteed enforcement now and in the future and EWG apparently voted against P3911 back in January [0], so perhaps they think that they won't ever see guaranteed enforcement if contracts is left in the standard as-is.

That's all quite speculative, though, so take that with an appropriately-sized grain of salt/

[0]: https://github.com/cplusplus/papers/issues/2536#:~:text=Dire...

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
Sure, but I think that has less to do with optimizations and more to do with avoiding potential footguns. The standard could, in principle, allow temporaries to bind to non-const references (and IIRC older versions of Visual Studio did do such a thing).

And even without such a change, such a restriction is pretty easily worked around without a const reference with something like T&& temp = get_daily_prices(); sum(temp);. I don't think that'll result in different codegen than either passing a temporary to a const ref or allowing binding a temporary to a non-const ref.

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
If you just want to avoid a copy without adding a null state then it's the reference bit that makes all the difference. Whether that reference is a const reference or not does not matter for optimization, I think.
aw1621107··on When a language's creator and its owners strongly disagree [pdf]
> Particularly const references, which provide potentially massive performance improvements

My understanding was that const references don't provide that much in the way of optimization opportunities (if any?) due to aliasing and the existence of const_cast.

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
> What does guaranteed enforcement mean?

I'm not sure I can express it better than the proposal to add the corresponding feature [0]:

> This extension addresses the Romanian National Body's (NB) concern by enabling the writing of an individual contract assertion that guarantees the program execution will not continue past the contract assertion if it is violated, regardless of the semantics of other contract assertions. This addition to the C++26 Contracts facility allows reliable use of enforced contract assertions in large codebases with mixed contract semantics and also in hardened libraries.

> Is it worth the trade-offs?

Apparently some people/NBs feel quite strongly that it is.

> There's a reason why most code turns assert off in production and only does it in debug builds.

There's also a reason some codebases define asserts that aren't turned off in production in addition to debug-only asserts. The proposal lists multiple under Section 3.1 Existing Practice.

> and so if the code is trusted to work, you wouldn't want to check that it's working correctly.

Right, but sometimes you might think the consequences of such blind trust being misplaced are worse than the performance hit.

Static detection of impossible-to-satisfy contract conditions would be interesting, but as you say the technology is probably not ready for the kind of widespread use that inclusion in C++ would entail.

[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p39...

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
> and I’m not wholly sold on the “memory safety” of Rust when something like Python can just put the performance sensitive parts in C and have safety for everything else.

> Most languages are memory safe.

As I said, there's more to Rust than just memory safety. If memory safety were the only thing Rust offered then I think it wouldn't have garnered nearly as much attention as it has. It's memory safety on top of everything else that makes it as interesting as it is.

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
> I tend to wonder, in an alternate reality, where the buck stopped at C for systems languages, would we be better off because more people would use it and know how to fix memory related issues?

I think that period has arguably already come and gone. C and C++ have been the most popular systems languages for decades. I think it's clear that mere popularity is not sufficient to produce memory-safe software with those languages. If anything, those languages are experiencing somewhat of a renaissance these days and it's these more recent developments that end up producing improvements with regard to memory safety.

> I just wonder why don’t we just keep using C, C#, and Java? I mean the former two are memory safe, and they can do “unsafe” just like Rust.

Bit of a nitpick, but I think you meant "latter" instead of "former".

That aside, there's more to picking a language to use than just memory safety and the ability to perform "unsafe" operations. Take your pick from more technical reasons (zero-overhead FFI to C, low-level control over data structures/operations/runtime, etc.) and more "soft" reasons (language affordances/features, ecosystem, etc.).

> Another thing: why do we write pseudocode and not design languages to look like pseudocode?

Perhaps COBOL might be what you're looking for? :P

As with many things, language design is full of tradeoffs, particularly when you want to produce something that is not too unfamiliar to a certain audience. IIRC another factor is that languages that want to work well for "experts" will tend to favor different points on the design spectrum. Consider APL-family languages - those are completely incomprehensible to anyone not familiar with them, but that same terseness is highly valued by proficient practitioners and is something pseudocode-like (or even "regular" code-like!) could never replicate.

aw1621107··on When a language's creator and its owners strongly disagree [pdf]
I think "guaranteed contract enforcement" should be added to item D since that is explicitly called out as the only technical proposal that would change some NB votes from no to yes.
aw1621107··on *sshole, or when are we going to get rid of C?
Sounds like you're far more qualified than I am to be discussing Rust's syntax choices/changes! I haven't been around nearly as long.

> The downside to using familiar syntax in a radically different language is that the differences really catch you by surprise.

Definitely agreed. It's a fine needle to thread for devs and I would not be surprised if there were tradeoffs to be made between familiarity and not importing too much of the inspiring language.

> I thought a lot about starting a blog to document the real stumbling points I hit, but it would be tedious and whiny, and I'm not interested in building an audience.

For what it's worth I personally think I'd find such a thing interesting to read. Sounds like it could make for some good feedback for better onboarding as well (well, assuming you're not talking about stumbling points from your first forays into Rust. But even those can still have some intellectual value, if the interest in Graydon's reflections on Rust's early days are any indication)

aw1621107··on *sshole, or when are we going to get rid of C?
> I guess language (and now community) wars can't help but devolve into this.

I suppose I'm a bit more optimistic than you; I feel that most commenters tend to do a pretty good job of not veering into unrelated topics, even when things start getting heated. The lobste.rs discussion on this post, for instance, seems to have avoided these tangents.

> As for strangeness, all three of these are valid:

To mangle a quote from a certain movie, "The strangeness budget is more what you'd call 'guidelines' than actual rules" :P

I agree that most C++ programmers would expect what you showed, but as with many things the turbofish is a tradeoff (unfamiliar syntax vs. ease of parsing). I presume the Rust devs at the time thought the tradeoff was worth it and there just hasn't been sufficient consensus/motivation to change it since then.

I recall someone here talking about the oddity you mention, but I don't remember if they said it was an implementation quirk or something more fundamental to Rust's grammar.

aw1621107··on *sshole, or when are we going to get rid of C?
Kind of curious where all this syntax talk came from given neither the article nor other comments are talking about it. Much of the rest seems to have a tenuous connection at best to the comment you are replying to.

That being said...

> For instance, why copy C++'s "::" for paths when Rust was a greenfield programming language without the burden of backwards compatibility?

The concept of a strangeness budget [0] is probably relevant here - if you want to appeal to a particular audience (C++ programmers in this case), you probably want to limit "strangeness" to places where it's likely to make the largest impact. From this lens it might be interesting to look at your question from the other direction: What would the benefit be of not copying C++'s syntax in this context, if any, and is that worth spending part of your strangeness budget?

Also this comment [1] from a few days ago seems well-timed, though it's answering a slightly different question:

> The `::` syntax allows the compiler to identify and resolve all the static paths before any type inference or other analysis. This is important because macros are expanded between these stages, and can in turn introduce more static names which must also be resolved before any further compilation.

Unfortunately I haven't been able to find an authoritative explanation of why :: instead of something else, though given how old it is [2] I'd guess it's one of those strangeness budget decisions.

> ("= Expression except LazyBooleanExpression or end with a }").

Based on the syntax diagram this bit is for let-else. If you look at the RFC [3] you'll get the answer you seek:

> While allowing e.g. if {} else {} directly in the expression position is technically feasible this RFC proposes it be disallowed for programmer clarity so as to avoid ... else {} else {} situations as discussed in the drawbacks section. Boolean matches are not useful with let-else and so lazy boolean expressions are disallowed for reasons noted in future-possibilities. These types of expressions can still be used when combined in a less ambiguous manner with parentheses, which is allowed under the two expression restrictions.

> Turbofish, could probably have been avoided by not using "<" and ">" both as type argument delimiters and as an operator for comparison. Scala 2 for example does it differently, using "[" and "]" for type argument delimiting.

Rust did in fact use square brackets for generics early on, but that was later changed to angle brackets [4, 5]. I haven't been able to dig up explicit discussions as to why, though again I suspect strangeness budget concerns were at least part of the reason.

Using square brackets for types is probably not a change that can be made in isolation either; at a minimum you'd need to consider how that interacts with indexing/function calls/etc. and how those in turn interact with traits (e.g., if manual implementation of function traits is ever stabilized).

Also, from what I understand the turbofish was never strictly necessary, and in fact it actually is possible for Rust to eliminate the need for the turbofish thanks to the magic of editions ([6]). It's "just" a matter of someone taking up the cause and gathering sufficient consensus.

> Using two different operators for what one could call "type annotations", namely both ":" and "->", in different cases, feels ugly to me.

You might find this email from Graydon Hoare interesting [7]. Among other things he says:

> If the argument's about consistency, I think the consistent thing to do is to keep ":" as a separator between bindings and types. My eyes get a little fuzzy guessing the associativity of "let x : fn(T) : U;" in a way that they do not when reading "let x : fn(T) -> U;". But I suppose reasonable people could disagre, or phrase the consistency requirement differently. My math professors would cry.

[0]: https://steveklabnik.com/writing/the-language-strangeness-bu...

[1]: https://news.ycombinator.com/item?id=50369228

[2]: https://web.archive.org/web/20130118053116/http://static.rus...

[3]: https://rust-lang.github.io/rfcs/3137-let-else.html

[4]: https://github.com/rust-lang/rust/commit/70b009178cf050d5bb7...

[5]: https://github.com/rust-lang/rust/commit/0107d6a81d2180d3a1e...

[6]: https://github.com/rust-lang/rfcs/pull/2544

[7]: https://web.archive.org/web/20201111221825/https://mail.mozi...

aw1621107··on *sshole, or when are we going to get rid of C?
Yes, this is explicitly discussed in the post:

> This is not a memory safety vulnerability; rather, it is one of those issues that, people often say, languages with better type systems won’t save you from. This is true! You can very well write a Rust function that returns "" if the password is too long. But the big difference between C and Rust in this regard is that the norms of the Rust community do not allow that.*

aw1621107··on C++26 Disapproved
> like scheduling certain snap elections to happen with very short notice at times of the day when people who would oppose his point of view would likely be asleep.

...He has the power to do this? And for which elections, exactly? The main go/no-go votes are the ones at the big meetings and I'd imagine there's not a lot of room for scheduling shenanigans there.

aw1621107··on C++26 Disapproved
> lifetime annotations

Say what!?

aw1621107··on Spooky C++
The other comments explained the same-length part.

As for the rest of the bug, my guess is:

1. Construct a temporary std::string for "foobarbazbug". The length of this string is 12 so it's stored on the stack due to the short/small string optimization.

2. Call Sanitize, passing a pointer to the temporary

3. Store the return value of Sanitize. Note that this return value is basically a pointer to the entire contents of the temporary, which due to SSO means the return value is actually a pointer to a specific bit of stack memory

4. Destroy the temporary.

5. Construct a std::string for the admin_id, reusing the stack slot for the temporary. This data also ends up on the stack due to SSO.

6. username == admin_id. username points to the stack memory formerly occupied by the argument passed to Sanitize(), which is now occupied by admin_id. As a result this comparison is effectively admin_id == admin_id, which is obviously true.

I suspect (but can't check for now) that due to the dependence on SSO lengths up to 16 should work in general, up to 22 (or 23? Can't remember) should work for libc++, and beyond that is not very likely to work at all.

aw1621107··on Spooky C++
> But it's not actually taken as a reference (with &) anywhere I see...

That is effectively what is happening internally. A string_view is morally a pointer/length pair; that pointer has to be initialized somehow.

> and even if it's just internally referencing the (static) string

It's not. The function call constructs a temporary std::string from the string literal, but once that is done the contents of the temporary are entirely independent from the string literal.

aw1621107··on Much Ado About None: Sentinel Values in Python
Yeah, that's more like what you would do if you wanted to translate the entire concept to Python instead of just the signature, and I probably should have mentioned that alternative as well (or should have said "A Python solution"). Can't say I've seen this approach used in practice, interestingly, though I'm probably not nearly as familiar with the Python that exists out there as most other commenters here.

The name clash is unfortunate, but luckily naming things is famously one of the easiest problems in CS so that should be trivial to solve :P

aw1621107··on Much Ado About None: Sentinel Values in Python
> It returns no owner if the document is deleted or just does not exist, which is accurate.

"I don't know" is not the same as "definitely no owner". You may handle the two answers the same for a particular scenario, but then again you may not.

Again, consider the case where get_owner is working on some kind of cache (in which case sure, the function name could stand to be improved). If the document is no longer in the cache it could be because it was actually deleted or because it was simply evicted to make room for other entries. In general you cannot assume that a missing cache entry is equivalent to the document existing and having no owner.

Or similarly (and also yet again), consider the case where get_owner is working on a versioned filesystem, in which case the outer optional could indicate that the (generational) document ID is out of date. In such a case treating "I don't know" and "no owner" the same is incorrect because the owner could have been updated from "no owner" to something else (such a case would likely be more realistically handled by returning a Result<Option<UserId>, SomeErrorKind>, though, given the myriad other ways a file system handle can go stale)

> Why do you need get_owner to tell you if the document exists or not?

Because sometimes there is no good alternative. Asking a cache whether it has an entry and then trying to get the entry value in a separate operation (particularly in a concurrent environment) is a classic setup for TOCTOU errors. You can say something similar for the versioned file system example.

> If anything it should be an error if the document doesn’t exist because you’ve violated a precondition.

I mean, enabling that kind of handling is precisely the point of the outer optional?

aw1621107··on Much Ado About None: Sentinel Values in Python
If you're going to use the returned value you'd probably return the moral equivalent of Rust's Result/Haskell's Either/etc. types. As long as the returned types don't happen to share the necessary API(s) the type checker should yell at you if you don't branch on the returned value.

I don't think Python has the equivalent of C++'s [[nodiscard]] or Rust's #[must_use], though, so if there are cases where the return value can legitimately be ignored then return values risk missing errors that would show up under exceptions.

aw1621107··on Switch Lowering in GCC
Hrm, interesting. First time I've heard of such a thing. Would have expected the Rust devs to have rolled back emitting noalias if it were still causing miscompiles. A quick glance through the bug tracker didn't turn up anything obvious, though I wouldn't be surprised if I just missed it.

For what it's worth, according to Godbolt the test program in your probe seems to work correctly for Clang 12.0.0 and later, which IIRC corresponds to when Rust (re-(re-?))enabled emitting noalias.

aw1621107··on Much Ado About None: Sentinel Values in Python
As I said, that sounds like a recipe for potential TOCTOU errors.

But again, this was an off-the-cuff example that I explicitly described as contrived from the start. I could probably flesh it out to make more sense, but I think it'd be easier just to refer to sibling comments rather than bikeshed this to death.

aw1621107··on Much Ado About None: Sentinel Values in Python
> In both cases getting no owner should suffice?

Depends on what exactly you plan on doing with the information, I'd imagine. I don't think it's necessarily a given that "no document" and "no owner" should be handled identically (e.g., searching for the updated document to try again vs. returning an operation-not-allowed error).

> Why are you using get_owner to try and figure out if the document exists?

Potential TOCTOU issues, perhaps? As I mentioned the document could have vanished out from under you so detecting this situation could let handle things appropriately (though to be fair that argument could probably extend to future operations as well).

Maybe a versioned filesystem would have made a bit more sense, where None would indicate a new version is available? Another example I've seen mentioned is trying to get a key-value pair from a JSON where the key may be missing.

But in any case, I did say it was a bit of a contrived example, so cut me a bit of slack :P It was just the first thing that came to mind.

The examples sibling comments give are probably more practical.

aw1621107··on Much Ado About None: Sentinel Values in Python
The idea is that optional values are either some value or no value, but sometimes the inner "some value" bit can itself be an optional value (in Rust this would be Option<Option<T>>, in C++ it'd be std::optional<std::optional<T>>, in Python it'd hypothetically be Option[Option[T]], etc.). Such a situation gives you three possible states:

- You have an inner value and the inner value is something non-null (an integer, a string, another optional, etc.)

- You have an inner value and the inner value is null

- You do not have an inner value

The problem, from what I understand, is that Option[T] in Python is equivalent to the union type T | None, and union types are flattened (e.g., (T | U) | V is equivalent to T | U | V) and duplicates are ignored (e.g., T | U | T is equivalent to T | U). As a result, Option[Option[T]] is equivalent to (T | None) | None, which is equivalent to T | None | None, which is finally equivalent to T | None, which means you lose the distinction between "No inner value" and "Inner value is present but is None".

As a more concrete (albeit somewhat contrived) example, say there's some API you can query for the owner of a particular document ID where the document ID may or may not refer to an actual document (e.g., maybe you got it from a cache and the document has since been deleted). One way to express this would be (using Rust syntax)

    fn get_owner(id: DocumentId) -> Option<Option<UserId>> { ... }
So if you get None, the document does not exist; if you get Some(None), the document exists but it has no owner; and if you get Some(<id>) the document exists and it has an owner.

(This can probably be done better by creating new enums, but it's an example so give me a bit of slack)

A naive port of this signature to Python would be

    def get_owner(id: DocumentId) -> Option[Option[UserId]]: pass
But to type checkers this is equivalent to

    def get_owner(id: DocumentId) -> UserId | None: pass
So now you're in the pickle the OP describes: you can end up with a None, but you no longer knonw whether it means "no document" or "no owner". The Python solution is to basically create a different name for either the outer None (e.g., MISSING as in the OP) or the inner one (e.g., NO_OWNER or the like).
aw1621107··on Much Ado About None: Sentinel Values in Python
Pretty sure Python's union types don't nest, though. If you could nest optionals then I feel that reduces the need for separate sentinel types since e.g., you can distinguish between "value not present" (None) and "null value present" (Some(None))
aw1621107··on Switch Lowering in GCC
> restrict is also still broken there.

That's a bit surprising to me given how heavily Rust uses the underlying mechanisms. Do you mean specifically how restrict is used in Clang or is there still something in LLVM that is broken?

aw1621107··on The result of the C++26 DIS ballot
> and is no longer undefined behaviour to act on them

This is only partially true. From the proposal [0]:

> Moreover, a variable that has a well-defined, erroneous initial object representation under this proposal can still lead to undefined behaviour later on:

    void f(T&);
    void g(bool);

    void h() {
      T* p;    // uninitialized, has erroneous value (e.g. null)
      bool b;  // uninitialized, erroneous value may not be valid bool

      f(*p);   // dereferencing has undefined behaviour
      g(b);    // undefined behaviour if b is invalid
    }
[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p27...
aw1621107··on Bbtree: Fastest Ordered Map
Sure. I didn't mean to say that the author used CBMC, only that it's an example of model checking.

Incidentally, the docs don't seem to say what model checker was used. I don't think any of verus, shuffle, or ffs are technically model checkers (or at the very least they don't describe themselves as such)

aw1621107··on Bbtree: Fastest Ordered Map
My guess is https://en.wikipedia.org/wiki/Model_checking . C Bounded Model Checker [0] is a relatively well-known instance in the niche.

[0]: https://www.cprover.org/cbmc/

aw1621107··on Composite objects and C++26 the std:polymorphic type
It is similar in that the valueless state isn't absolutely necessary, but avoiding the valueless state in general is not free and it seems the committee was unwilling to force implementations to incur that cost.

The docs for Boost.Variant state that avoiding a valueless state requires some kind of additional storage beyond that strictly required for the intended value. In the current implementation this means that assignment may allocate (modulo optimizations described further down the page) [0], which is obviously unacceptable for some use cases.

The proposal for std::indirect and std::polymorphic describes similar costs for avoiding a valueless state, though for moves instead of assignment [1]:

> Without a valueless state, moving indirect or polymorphic would require allocation and moving from the owned object. This would be expensive and would require the owned object to be moveable. The existence of a valueless state allows move to be implemented cheaply without requiring the owned object to be moveable.

Presumably the committee made a similar decision with respect to a valueless state for std::variant as they did for std::polymorphic/indirect.

[0]: https://www.boost.org/doc/libs/latest/doc/html/variant/desig...

[1]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p30...

aw1621107··on C# now beats Rust on WASM size
IIRC no_std is a per-crate attribute and does not actually forbid use of std, so I think there's a decent chance you can use random Rust crates without changing them.

That being said, I do think it would have been nice if the author had chosen a more interesting benchmark, but alas...

Page 1 of 34Next →