[HN Gopher] map::operator[] should be nodiscard
       ___________________________________________________________________
        
       map::operator[] should be nodiscard
        
       Author : jandeboevrie
       Score  : 59 points
       Date   : 2025-12-19 15:45 UTC (5 days ago)
        
 (HTM) web link (quuxplusone.github.io)
 (TXT) w3m dump (quuxplusone.github.io)
        
       | reactordev wrote:
       | My pet peeve with c++ is exactly this. Either it's not wise to
       | call release, or it is (under circumstances) and yet the
       | developer has no idea whether their scenario applies (tip: it
       | doesn't, 90% of the time).
       | 
       | The stdlib is so bloated with these "Looks good, but wait" logic
       | bombs.
       | 
       | I wish someone would just draw a line in the sand and say "No,
       | from here on out, _this_ is how this works and there are no other
       | scenarios in which there needs a work around". This is why other
       | systems languages are taking off (besides the expressiveness or
       | memory safety bandwagon) is because there are clear instructions
       | in the docs on what _this_ does with examples of how to use it
       | properly.
       | 
       | Most c++ codebases I've seen the last 10 years are decent (a few
       | are superb) and I get that there's old code out there but at what
       | point do we let old dogs die?
        
         | GuB-42 wrote:
         | C++ has always been a "kitchen sink" language, it is used in
         | many different ways and drawing any line may alienate an entire
         | industry.
         | 
         | > This is why other systems languages are taking off
         | 
         | Great! It is not a competition. If you think that Rust is a
         | better choice, use Rust, don't make C++ into Rust. Or maybe try
         | Carbon, it looks like it is the language you want. But if you
         | have some old dogs you want to keep alive, then use C++, that's
         | what it is for.
        
           | reactordev wrote:
           | I get it, I do. There's a lot of old code out there. My point
           | wasn't that old dogs are bad. My point was about changing how
           | we care for them.
           | 
           | If you have old code that you want to compile, use -c98 or
           | whatever to peg it to that. Leave the rest of us alone to
           | introduce more modern ways of things. I'd even be happy to
           | see removal of things.
        
         | pjmlp wrote:
         | > This is why other systems languages are taking off
         | 
         | For the time being that are still being written with C++
         | infrastructure though.
         | 
         | It would be great if those wannabe C++ replacements were fully
         | bootstraped.
        
           | reactordev wrote:
           | Go compiles go, not sure what you mean by wannabe c++.
           | 
           | There's a frontend to gcc for go and working on rust. Is it
           | the use of gcc you dislike? You're going to have to explain
           | some more.
           | 
           | We're stuck on ASM/ELF. We're stuck on C of some kind. Maybe
           | in the future LLMs can help us write low-level / high
           | expressiveness code but until we get rid of 1970s "personal
           | computer" decisions in silicon, we're stuck with it.
        
             | pjmlp wrote:
             | Go is not a wannabe C++ replacement.
             | 
             | It could be, but its designers aren't keen in modern
             | language design.
             | 
             | First it needs to fulfill more use cases than Docker and
             | Kubernetes ecosystem.
             | 
             | And while TinyGO and TamaGo exist, they require custom
             | runtimes, and Assembly tricks that C++ supports at the
             | language level, or even Rust does better than Go.
             | 
             | It is better than using Oberon-07 minimalist design,
             | though.
        
               | throwaway17_17 wrote:
               | Can you expand on Oberon-07 minimalism in the context of
               | bootstrapping or working at the lowest level of
               | abstractions?
               | 
               | Your posts about the Wirth tradition languages and their
               | implementations are typically well founded and I haven't
               | read much on this aspect. If you just have a reference
               | you'd suggest that would be more than enough (if you
               | don't want to take time explaining what has been written
               | elsewhere).
        
               | reactordev wrote:
               | I agree 100%. Just pointing out there's efforts in this
               | area for better or worse.
               | 
               | My rant is really about _sensible defaults_ that should
               | enforce security and standards (stdlib after all) instead
               | of having to juggle archaic edge cases from hardware of
               | 30 years ago or adding more keyword sugar to your
               | signature to make it through.
               | 
               | Go is fun to write though.
        
             | throwaway17_17 wrote:
             | What is the proposed replacement for ASM (in particular)
             | and C in the context of the bootstrapping process? Then why
             | lump ELF (unless you don't mean the executable format) in
             | with the low level language?
             | 
             | Historically, pjmlp has pushed very strongly for languages
             | attempting to take the place of C and C++ at the
             | infrastructure layer can not claim to have supplanted those
             | two until their own compiler and related infrastructure is
             | not dependent on C++ (LLVM in particular). I tend to
             | sympathize with this view, it is really hard to take a
             | language's claim to have supplanted C++ and be the only fit
             | for use language going forward, but then is dependent on
             | millions of lines of the languages they disparage.
             | 
             | As a counter however, it's rather difficult to expect a
             | language to overcome thousands of person-years of work on a
             | compiler like LLVM and tens of thousands of person-years on
             | Linux. The newer languages should be able to make an
             | articulate case that throwing away so much work is not a
             | viable approach and just use what exists now but keep the
             | new languages on all greenfield projects.
        
               | steveklabnik wrote:
               | As another counter, Rust has never claimed to have
               | "supplanted" C++. So holding it to that standard is
               | holding it to a non-goal for itself in the first place.
        
               | throwaway17_17 wrote:
               | tl;dr - Rust wants to be a foundational language of the
               | computing stack and holding it to the standard of being
               | bootstrapped, instead of relying on another language, is
               | a reasonable critique.
               | 
               | Clearly Rust the language and the associated organization
               | would not make that claim. Particularly where supplanted
               | is a past tense verb and indicates that it is a completed
               | project.
               | 
               | However, despite overblown complaints about the RESF, the
               | community both in commentary and in practice has been
               | extremely vocal that any language that does not have
               | Rust's memory safety model is not suitable for any new
               | project or further use in existing projects. And while
               | the RIIR meme is for the most part a message board
               | strawman, again, the community surrounding Rust is busy
               | reimplementing coreutils in Linux, putting Rust in the
               | kernel, and rewriting the userland executables that most
               | Linux workflows are based around (ripgrep being the most
               | successful in this group).
               | 
               | It is clear that Rust, the community of users (if not the
               | language as an independent entity) clearly wants to
               | supplant both C and C++ at all levels of the computing
               | stack. The push for Rust in the Linux kernel is enough
               | evidence to support the concept at the most pervasive
               | level.
               | 
               | Continuous references to wide spread adoption and
               | endorsements by 'big tech' is used to frame Rust as the
               | only viable option going forward. Blog posts, Reddit
               | threads, and board comments all routinely take the stance
               | that memory-safety (as defined by Rust) is 'table stakes'
               | for any development occuring in current year.
               | 
               | It feels disingenuous to pretend that Rust is not trying
               | to become the industry standard language in the way C and
               | C++ is today and has been for multiple decades. And given
               | that aim, I think talking about Rust, as the name for
               | both the language and its community of users and
               | supporters, is working to supplant C and C++.
               | 
               | Given all that, I find it fair to discuss the fact that
               | while busy trying to maneuver itself into every space in
               | the tech industry (from embedded all the way up to the
               | front end for web web apps) and find some success in
               | doing so Rust is still reliant on C++ infrastructure
               | particularly for compilation. I was responding to a pair
               | of comments about the desire to see languages that want
               | to be the bedrock of the computing stack bootstrapped. I
               | think Rust absolutely wants to be such a bedrock language
               | and as such, I don't think wanting it to be bootstrapped
               | and not reliant on the C++ it want to replace is an
               | unreasonable standard to hold the language to.
        
               | LegionMammal978 wrote:
               | Is offering an alternative to LLVM not precisely one of
               | the purposes of the rustc_codegen_cranelift backend [0]?
               | It still doesn't have 100% feature parity, but I believe
               | it's able to fully bootstrap the compiler at this point.
               | Writing a rustc backend isn't trivial, but it isn't as
               | impossible as you make it out to be.
               | 
               | [0] https://github.com/rust-lang/rustc_codegen_cranelift
        
               | throwaway17_17 wrote:
               | I'm not sure what I wrote to give the impression that
               | Rust was unable to write a compiler, let alone implied it
               | was impossible. Rust is certainly full featured enough to
               | write a very well performing compiler. I find my comment
               | more an indictment, and viewed uncharitably an accusation
               | of hypocrisy, of the language org's oversight that they
               | are so heavily invested in LLVM (but if I was leveling
               | such an accusation it would not be just because it's a
               | C++ project)
               | 
               | My comment was focused on the fact that Rust is not using
               | a Rust compiler and therefore is relying on deep and
               | complex C++ infrastructure while working to supplant the
               | same at the lowest levels of the computing stack.
               | 
               | I was also commenting, up the thread, in a chain of
               | comments about a perceived shortcoming of Rust's
               | implementation (i.e. it's not being bootstrapped) and why
               | some people view that as a negative.
        
               | tialaramex wrote:
               | All of the _front-end_ is in fact pure Rust, I know that
               | because I am one of the huge number of authors. The
               | backend, thus the code generation and many optimisations
               | of the sort AoCO is about is LLVM.
               | 
               | We absolutely know that if Rust didn't offer LLVM you'd
               | see C++ people saying "Rust doesn't even have a proper
               | optimiser". So now what you're asking for isn't just "a
               | Rust backend" which exists as others have discussed, but
               | a Rust alternative to LLVM, multiple targets, lots of
               | high quality optimisations, etc. presumably as a drop-in
               | or close approximation since today people have LLVM and
               | are in production with the resulting code.
        
               | reactordev wrote:
               | Ignore them. Keep going. Debates like these are "Since
               | you said X, Y can't be true" kind of debates. As long as
               | you have access to be able to do assembly, you should be
               | able to do this. I say you because this is way out of my
               | wheelhouse. I just want a cleaner, less mine-field laden,
               | OO language that compiles to machine code. That's it. We
               | can stick a feather in this until this time a decade from
               | now when we complain about it again.
        
               | steveklabnik wrote:
               | "Rust is a good language and we should use it to write
               | software" is not the same thing as "lol C++ sucks and
               | nobody should use it for anything ever."
               | 
               | Engineering is all about tradeoffs. Responding to
               | perceived zealotry with more, but different, zealotry
               | makes it harder to have actual discussions.
               | 
               | LLVM is best in class at what it does. Until someone else
               | decides to make something like LLVM in Rust, it's not
               | realistic to use something else. That's just engineering.
               | The choices here directly _refute_ these sorts of
               | zealotry claims, that is, it 's not incoherence in what's
               | being done, it's that you are attributing something to a
               | large group of people who have a wide variety of beliefs.
               | Overall, people are more pragmatic than you're giving
               | them credit for, that's why rustc uses LLVM.
        
           | SJC_Hacker wrote:
           | > t would be great if those wannabe C++ replacements were
           | fully bootstraped
           | 
           | This would require (re)writing the OS in the replacement
           | language
           | 
           | Also need assembler to be taken seriously, which Rust can't
           | do last I checked
        
             | throwaway17_17 wrote:
             | Can you explain the assembler bit? Are you talking about
             | the handling of inline assembly? I though Rust allowed that
             | in unsafe code.
        
               | steveklabnik wrote:
               | It does! And it's in the language proper, unlike being a
               | compiler extension like it is in C.
        
               | tialaramex wrote:
               | And Rust also has a good story for all the accompanying
               | baggage such as naked assembler functions, whereas even
               | with your extension in C or C++ there may just be a shrug
               | emoji, or some blog posts because hey it's not part of
               | the language.
        
             | pjmlp wrote:
             | Nah, they could start by not depending on LLVM/GCC and do
             | their whole compiler back to back.
        
               | lenkite wrote:
               | Isn't that what Rust is attempting with Cranelift ? They
               | had making Cranelift backend "production-ready" for
               | development use as a goal for 2026. I am guessing it will
               | be a few years beyond that before it is made available
               | for general production-ready use.
               | 
               | I think Zig might possibly beat Rust's timeline here for
               | a "No C/C++" toolchain. That is if its lead doesn't burn
               | himself out.
        
       | fn-mote wrote:
       | Title could be "ugly C++ idioms prevent map::operator[] from
       | being [[nodiscard]]".
       | 
       | Many of the uses are in Google's codebase.
       | 
       | Overall very technical- interesting if you are a library writer
       | or maybe if you care about long term improvements in your
       | C++'legacy codebase.
        
       | rwmj wrote:
       | https://en.cppreference.com/w/cpp/language/attributes/nodisc...
       | .. in case anyone else was wondering. It seems to mean the
       | compiler should warn if you ignore the result except by an
       | explicit cast to void.
        
         | larusso wrote:
         | Thanks. I was wondering what this means practically. So they
         | rolled this back because Google who compile with warnings as
         | errors can't fix these lines? Must be great to be Google and on
         | all these boards. I on the other hand have to deal constantly
         | with breaking changes left and right because someone decided,
         | among them Google (16KB page tables anyone), that going forward
         | stuff works differently.
        
           | compiler-guy wrote:
           | Google could easily change these lines. The question is,
           | should it?
           | 
           | One thing about Google living so close to head with its
           | libc++ is that it encounters the issues downstream users will
           | encounter, just long before everyone else. It saw this
           | development within a day or two of the or getting merged.
           | 
           | The idiom is unfortunately common in C++ codebases around the
           | world so this was a good predictor that many other users will
           | be broken. It isn't necessarily erroneous, unlike many of the
           | other no-discard additions made in this patch series.
           | 
           | So the question becomes, "Are the false positives worth the
           | true positives?" Not just for Google, but for the entire user
           | base.
           | 
           | It is reasonable to disagree on this, and often library
           | writers up to date on the latest and greatest miss the issues
           | this sort of change will cause.
        
       | junon wrote:
       | For the Rust inclined, [[nodiscard]] is #[must_use], if you were
       | confused.
       | 
       | Anyway, this article illustrates a great reason why C++ is a
       | beautiful mess. You can do almost anything with it, and that
       | comes at a cost. It's the polar opposite ethos of "there should
       | be one clear way to do something" and this sort of thing reminds
       | me why I have replaced all of my systems language needs with Rust
       | at this point, despite having a very long love/hate relationship
       | with both C and C++.
       | 
       | Totally agree it should be marked as nodiscard, and the reasoning
       | for not doing so is a good example of why other languages are
       | taking over.
        
         | bayesnet wrote:
         | It's also worth noting that in rust you don't need to be as
         | worried about marking a function #[must_use] if there is a
         | valid reason some of the time to discard the value. One can
         | just assign like so `let _ = must_use_fn()` which discards the
         | value and silences the warning. I think this makes the intent
         | more clear than casting to void as TFA discusses.
        
           | on_the_train wrote:
           | There is in c++, too (std::ignore). Not sure why the author
           | decided to go with the ancient void cast
        
             | aw1621107 wrote:
             | I believe C++26 now allows _ as a placeholder name [0]:
             | 
             | > We propose that when_ is used as the identifier for the
             | declaration of a variable, non static class member
             | variable, lambda capture or structured binding. the
             | introduced name is implicitly given the [[maybe_unused]]
             | attribute.
             | 
             | > In contexts where the grammar expects a pattern matching
             | pattern,_ represents the wildcard pattern.
             | 
             | Some of the finer details (e.g., effect on lifetime and
             | whether a _ variable can be used) differ, though.
             | 
             | [0]: https://github.com/cplusplus/papers/issues/878
        
               | tialaramex wrote:
               | Specifically, Rust's _ is _not_ a variable, it is a
               | pattern that matches anything and so let _ = isn 't an
               | assignment it's specifically the explicit choice not to
               | assign this value. If we wrote a "dummy" variable the
               | compiler is forbidden from dropping the value early, that
               | "dummy" is alive until it leaves scope, whereas if we
               | never assigned the value it's dropped immediately.
               | 
               | In modern Rust you don't need the let here because you're
               | allowed to do the pattern match anywhere, and as I said _
               | is simply a pattern that matches anything. So we could
               | omit the let keyword, but people don't.
        
             | vitus wrote:
             | std::ignore's behavior outside of use with std::tie is not
             | specified in any finalized standard.
             | 
             | https://www.open-
             | std.org/jtc1/sc22/wg21/docs/papers/2023/p29... aims to
             | address that, but that won't be included until C++26 (which
             | also includes _ as a sibling commenter mentions).
        
         | the_mitsuhiko wrote:
         | Interestingly Index::index is also usually not marked as
         | `#[must_use]` in Rust either.
        
           | junon wrote:
           | I don't believe you can mark trait methods with #[must_use] -
           | it has to be on the implementation. Not near a compiler to
           | check at the moment.
           | 
           | In the case of e.g. Vec, it returns a reference, which by
           | itself is side-effect free, so the compiler will always
           | optimize it. I do agree that it should still be marked as
           | such though. I'd be curious the reasons why it's not.
        
             | the_mitsuhiko wrote:
             | But it's also not marked at the implementation for
             | HashMap's Index impl for instance.
        
               | tialaramex wrote:
               | This didn't seem like a footgun to me, hats["Jim"]; will
               | panic if, in fact "Jim" isn't one of the keys, but what
               | did the hypothetical author expect to happen when they
               | write this? HashMap doesn't implement IndexMut so
               | hats["Jim"] = 26; won't even compile.
        
             | steveklabnik wrote:
             | This is just my take, but I think historically the Rust
             | team was hesitant to over-mark things #[must_use] because
             | they didn't want to introduce warning fatigue.
             | 
             | I think there's a reasonable position to take that it
             | was/is too conservative, and also one that it's fine.
        
         | pjmlp wrote:
         | I disagree with the conclusion, other languages are taking over
         | because they have the advantage of not having 40 years of
         | production code history, and those adopting them don't care
         | about existing code.
         | 
         | You will find similar examples in Python, Java, C#,... and why
         | not everyone is so keen into jumping new language versions.
        
         | m-schuetz wrote:
         | I'm not a fan of nodiscard because it's applied way too freely,
         | even if the return value is not relevant. E.g. WebGPU/WGSL
         | initially made atomics nodiscard simply because they return a
         | value, but half the algorithms that use atomics only do so for
         | the atomic write, without needing the return value. But due to
         | nodiscard you had to make a useless assignment to an unused
         | variable.
        
       | dooglius wrote:
       | `try_emplace` is not a huge improvement since it overloads the
       | existing keyword "try" to mean something pretty different. Should
       | be `emplace_if_absent`/`insert_if_absent` but changing the API of
       | stdlib would require going through a huge formal process
        
       | themafia wrote:
       | Ah, and because this is C++, the standard map having typed
       | template parameters, which could be a non pointer, they're forced
       | to make operator[] have this semantic:
       | 
       | Returns a reference to the value that is mapped to a key
       | equivalent to key or x respectively, performing an insertion if
       | such key does not already exist.
       | 
       | Which is a bit of a surprise coming from mostly C and Go.
        
         | ahartmetz wrote:
         | This "create a default-constructed value just so you can return
         | a reference" logic is pretty terrible tbh. For insert: first
         | create a default-constructed value, then assign to it. For
         | retrieval: in the not found case, (permanently) insert a
         | default-constructed value into the map. Need to return a valid
         | reference!
         | 
         | Qt containers do it better: upsert with insert() and retrieve
         | with value(), which, in the not found case, will return a
         | default-constructed value (or a caller-supplied value) but
         | _without_ inserting it into the map.
        
       | j1elo wrote:
       | C++ could try to approach the "stability without stagnation"
       | model.
       | 
       | Add an opt-in compiler flag --edition='26' which, when used,
       | applies the breaking changes defined for C++26. Then users like
       | Google or others who have been (ab)using some features for their
       | side effects can decide to stay on the older versions.
        
         | pjmlp wrote:
         | It already exists, --std=c++26.
        
       | ivanjermakov wrote:
       | Another default that Zig got right: every non-void result must be
       | handled.
       | 
       | https://github.com/ziglang/zig/issues/219
        
         | kyralis wrote:
         | I would not agree. There are plenty of times I've written and
         | used functions that have informational return values that are
         | beneficial in certain cases and unnecessary in most. This is
         | why most languages have chosen annotations to allow for the
         | function author to indicate intent.
        
         | m-schuetz wrote:
         | Terrible for atomic functions where you are often only
         | interested in atomically writing something, but not interested
         | in the return value they also provide.
        
         | leecommamichael wrote:
         | This is one of those preferences that will never fail to split
         | the room. I appreciate both routes depending on the domain, but
         | I do have a preference. As a games and UI app developer, I find
         | required-handling-by-default adds too much friction and
         | disrupts my flow. Rust and Zig (through different means) create
         | friction like this in an effort to make low-level code
         | "easier", but only if the code is "correct" according to the
         | language. As a dev that spent a lot of time with Swift, and
         | loving the language's ability to express APIs, I came to
         | appreciate compilers with the quality that "if the code runs,
         | it's probably correct," and yet my preference did not land on
         | Rust or Zig, but with Odin. I sat down with Odin 5 or so years
         | ago and it felt like the friction was exactly where I wanted it
         | to be for the software I write.
        
         | drnick1 wrote:
         | It's a terrible idea, what if you aren't interested in the
         | return value and can't rewrite the function because it is in a
         | third party library? Assigning that to a variable that won't be
         | used is terribly inefficient and inelegant.
        
       | dundarious wrote:
       | There is no need to compromise in order to support pre-C++17, you
       | don't need `try_emplace` when the value is like bool and hence
       | doesn't benefit from move semantics -- plain old `insert` is
       | exactly equivalent, and has existed since std::map's inception.
        
       | nialv7 wrote:
       | C++ operator [] is poorly designed: index, and index+assignment
       | should be two different operators, and indexing alone should
       | never insert new entries into the map.
       | 
       | Languages like D [0] or Rust [1] get this right.
       | 
       | [0]:
       | https://dlang.org/spec/operatoroverloading.html#index_assign...
       | [1]: https://doc.rust-lang.org/std/ops/trait.IndexMut.html
        
         | gpderetta wrote:
         | You could (and I would) make the opposite statement: upsert
         | should be the default operator and if you want lookup only or
         | insert only you call different operators.
         | 
         | I find it annoying that I often have to reach to defaultdict in
         | Python to get this behavior.
        
           | hmry wrote:
           | I certainly use defaultdict often in Python too, but not more
           | often than the regular dict. Maybe 90% dict and 10%
           | defaultdict. So from my POV lookup only should definitely be
           | the default.
        
           | tialaramex wrote:
           | C++ could offer the entry API here, so you can get back a
           | type representing the result of finding where this key would
           | go, and then either it has a key+value pair you can mutate if
           | you want, or it has a blank state allowing you to write a new
           | key+value pair if that's what you want, without redoing the
           | potentially expensive find operation to figure out where to
           | put the new/updated pair
        
       | jesse__ wrote:
       | lol .. add one more reason to the overflowing fountain of reasons
       | to not use anything in std
        
       ___________________________________________________________________
       (page generated 2025-12-24 23:01 UTC)