[HN Gopher] Rust Weird Expressions
       ___________________________________________________________________
        
       Rust Weird Expressions
        
       Author : linkdd
       Score  : 213 points
       Date   : 2021-05-14 11:01 UTC (11 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | krick wrote:
       | Yeah... I didn't want to admit that for a really long time, but
       | Rust isn't really a huge success. You can obfuscate the code in
       | most (all?) of the languages, but the fact that all of these
       | things are even possible, definitely isn't something to be proud
       | of.
        
       | mjbrusso wrote:
       | Wouldn't it be time to create a International Obfuscated Rust
       | Code Contest (IORCC)?
        
         | steveklabnik wrote:
         | We ran something similar in 2016: https://blog.rust-
         | lang.org/2016/12/15/Underhanded-Rust.html
        
         | smoldesu wrote:
         | I like the idea, but I think Rust is too picky of a language to
         | use for something like that. IOCCC is fun because of how
         | interesting it is to watch seasoned C professionals finesse
         | their favorite language. A Rust variant would likely end in
         | tears, copious compiler warnings and perhaps blood.
        
         | linkdd wrote:
         | Isn't it just a normal Rust program ?</troll>
        
       | skohan wrote:
       | I have no idea what I am reading
        
         | phaylon wrote:
         | It's the part of the Rust compiler test suite that makes sure
         | odd syntactical combinations still behave as they are supposed
         | to.
        
           | chrismorgan wrote:
           | And I guess it's 2015 edition, because it'll fail in 2018
           | edition Rust: where the 2015 edition has ::u8(0u8), the 2018
           | edition will need you to write crate::u8(0u8) or
           | self::u8(0u8), since only crates exist at the top level now,
           | rather than the contents of the current crate as well.
        
             | est31 wrote:
             | Yeah it's indeed the 2015 edition because while compiletest
             | watches for _/ / edition:something_ comments [0] (like this
             | [1]), it doesn't pass any edition flag to the compiler if
             | no such comment is present, and the rust compiler defaults
             | to the 2015 edition if none was specified.
             | 
             | [0]: https://github.com/rust-
             | lang/rust/blob/69b352ef7749825abde2d...
             | 
             | [1]: https://github.com/rust-
             | lang/rust/blob/69b352ef7749825abde2d...
        
             | Macha wrote:
             | However, the compiler has to continue to be able to compile
             | 2015 edition code, as it is still guaranteed to work and be
             | intermixable with 2018 (and soon, 2021) code in the same
             | application.
        
               | chrismorgan wrote:
               | For curious people unfamiliar with the situation with
               | this: Rust cares about backwards-compatibility so that
               | you should still be able to compile code from 2015 in
               | 2042 (some libraries are just stable!), but it also wants
               | to allow certain changes to the language that you might
               | think would be backwards-incompatible. Each crate
               | (library) declares an edition, and the compiler follows
               | the rules of that edition, and then you can link all the
               | different crates together without worrying what edition
               | they were written in. Editions are limited in what they
               | can change, because things like traits are shared between
               | crates, so they can't be changed in editions; it's mostly
               | syntactic stuff, like the 2018 edition making async/await
               | keywords and making dyn a full (rather than conditional)
               | keyword.
        
               | fat-chunk wrote:
               | I'm not very knowledgeable with how compilers or language
               | standards work, but would there not be security
               | implications with this approach?
               | 
               | For example let's say a security exploit surfaces in the
               | 2015 edition of Rust, would that not mean all the
               | libraries declared as 2015 edition would have to be
               | updated or abandoned in that case?
               | 
               | Or now that I think about it, is it instead the case that
               | a whole program including all dependencies will be
               | compiled by the same compiler (of which newer editions
               | will have the latest security fixes), just that the
               | compiler will always have to support compiling programs
               | using legacy syntax when it identifies the crate's
               | edition?
        
               | rabidferret wrote:
               | > Or now that I think about it, is it instead the case
               | that a whole program including all dependencies will be
               | compiled by the same compiler (of which newer editions
               | will have the latest security fixes)
               | 
               | It's this. Rust doesn't (yet) have a stable ABI for
               | functions that aren't marked `extern "C"`. Any security
               | vulnerability that would affect code in rust-lang/rust
               | would most likely be in the standard library, which
               | doesn't change between editions. All code links to the
               | same libstd. Only the compiler frontend changes
        
               | greenshackle2 wrote:
               | It's just syntax differences. The newer compiler supports
               | all previous language editions, you're not using a
               | 2015-era compiler to compile 2015 edition code.
               | 
               | Rust is not ABI-stable, there is no guarantee that you
               | can even mix libs built with different versions of the
               | compiler. The entire Rust tooling is built around static
               | linking and building all your dependencies from sources.
               | So yes, all the crates that go into your program are
               | built with the same compiler, it's just that the compiler
               | knows how to paper over the syntax differences in the
               | different language editions.
        
               | peterkelly wrote:
               | This is what Python should have done.
        
               | winstonewert wrote:
               | This approach only works for syntax level changes, it
               | wouldn't have helped the problematic changes in Python 2
               | -> 3.
        
               | cyphar wrote:
               | Well, the ability to specify python version by module
               | would've made migration much easier for everyone (in
               | theory). But you're quite right that it wouldn't by
               | itself be a solution -- it would also have required
               | additional complexity to handle interoperability when
               | calling between python versions, both in the runtime and
               | the programs themselves (even a sufficiently smart
               | compiler can't figure out what string encoding a python2
               | function expects).
        
               | egeozcan wrote:
               | JavaScript had something close, valid on the context
               | level, with "use strict" (which is, I guess, borrowed
               | from Perl), and I still don't understand why they don't
               | repeat that for newer features that would be much simpler
               | if they broke backwards compatibility.
        
               | iudqnolq wrote:
               | There's even a handy macro for switching between them /s
               | 
               | https://twitter.com/m_ou_se/status/1392200805168689154
        
             | phaylon wrote:
             | Yeah. I'd assume a 2018 specific one would show up below
             | https://github.com/rust-
             | lang/rust/tree/master/src/test/ui/ru... if needed.
        
         | berkes wrote:
         | Me neither. But I'd guess it's to test (or explain, or
         | document) edge-cases or unexpected behaviour when using Rust
         | std.
        
           | Minor49er wrote:
           | I've seen those sorts of things in other languages, though
           | they're often accompanied by explanatory comments, if not a
           | separate document. Given that these appear to be in a test
           | suite for Rust itself, I'm surprised that these aren't
           | explained in the code (at least as far as I saw)
        
       | iudqnolq wrote:
       | I'd like to nominate anything by Mara Bos (@m_ou_se) on Twitter.
       | For example:
       | 
       | > What's your favourite @rustlang boolean? Option<()>,
       | Option<Option<!>>, std::iter::Once<()>, std::fmt::Result
       | 
       | > tired: u16, wired: BTreeSet<BTreeSet<BTreeSet<BTreeSet<()>>>>
       | 
       | > What's your favourite @rustlang integer? &[()],
       | Enumerate<Repeat<()>>, Poll<File>, *const PhantomData<usize>
       | 
       | @jaaaarwr> How is Vec<()> not in this poll?
       | 
       | > That's just the owning version of &[()], right? Why would you
       | need to own ()s? ;)
       | 
       | Edit: She's also the author of inline_python!, a serious macro
       | for interspersing code that calls python (including local
       | variable interop), and whichever_compiles!, a joke macro that
       | takes multiple expressions, forks the compiler (as in a fork
       | syscall), and compiles the first one that compiles to valid rust.
        
         | estebank wrote:
         | Result<(), ()> is just a spicy bool.
         | 
         | A lot of people are on board with not using if/else and instead
         | :)                   match x {             true => {}
         | false => {}         }
        
           | skrtskrt wrote:
           | > Result<(), ()> is just a spicy bool.
           | 
           | EDIT: quoted wrong part of parent, ignore above
           | 
           | > match x { true => {} false => {} }
           | 
           | I really like this. Pattern matching syntax is great, nice to
           | use in place of if/else as much as possible
        
             | OJFord wrote:
             | Hang on aren't we conflating two things in this thread? Or
             | you quoted the wrong half of GP's comment perhaps? You can
             | pattern match on an actual `bool`; if you use `Result<(),
             | ()>` instead you'll have to match on `Ok(_)`/`Err(_)`,
             | which is bad because one of your Boolean values is an
             | 'error' now, and also because which one is is up to you (to
             | remember)!
        
             | iudqnolq wrote:
             | I disagree. There's a clippy lint against pattern matching
             | on bool. I also think Result has the connotation of
             | "success or error". Plus it's always annoying when a
             | function returns an error that doesn't implement Error and
             | I have to wrap/replace it to make it integrate with the
             | standard error handling ecosystem.
             | 
             | There's a nice alternative though, which is empty struct
             | errors.                   #[derive(Debug, thiserror::Error,
             | displays of::Display)]         /// Frobnicating the foo
             | failed.         struct FrobError;
             | 
             | The advantage is the standard advantages types use, i.e. if
             | I want to propagate it I know this specific zero sized type
             | has a specific meaning. It'll also show a nice message if
             | you print it.
        
               | estebank wrote:
               | I like matching on bools on the same cases where someone
               | wouldn't write braces around the body in C: whenever the
               | corresponding expressions keep you under the line length.
               | For example:                   let plural = match
               | cases.len() > 1 {             true => "s",
               | false => "",         };
               | 
               | Of course, you'd more likely have something like
               | let prefix = match cases.len() {             0 => "no
               | values".to_string(),             1 => "a
               | value".to_string(),             n => format!("{} values",
               | n),         };
        
               | rabidferret wrote:
               | let plural = match cases.len() {             1.. => "s",
               | 0 => "",         };
               | 
               | ;P
        
               | cyphar wrote:
               | That's not the correct plural rule in English, you'd want
               | let plural = match cases.len() {             0 | 2.. =>
               | "s",             1 => "",         };
               | 
               | or something (though to be fair the original code snippet
               | also handled "0" incorrectly -- should be "cases.len() !=
               | 1").
        
               | iudqnolq wrote:
               | And also to be fair you probably don't want to be
               | hardcoding English pluralization rules.
        
               | spullara wrote:
               | My favorite thing about internationalization is that some
               | languages have 6 different translations for different
               | amounts.
        
               | iudqnolq wrote:
               | Interesting. I prefer writing that as an if/else on one
               | line when rufmt lets me, but I see your point.
        
         | Tuna-Fish wrote:
         | > whichever_compiles!, a joke macro that takes multiple
         | expressions, forks the compiler (as in a fork syscall), and
         | compiles the first one that compiles to valid rust.
         | 
         | This is inspired.
        
           | iudqnolq wrote:
           | It's definitely worth reading the full thread if you haven't
           | 
           | > Did you know you can just fork() the @rustlang compiler
           | from your proc_macro? :D
           | 
           | > This is, of course, a horrible idea.
           | 
           | > Unfortunately rustc is multithreaded and fork() only forks
           | one thread. We'll just assume the other threads weren't doing
           | anything important.
           | 
           | > In today's rustc, this means we're only missing the main
           | thread in the fork. But that one was only going to call
           | exit() after joining our thread anyway, so not very
           | important. It just means the process always exits with a
           | success status. So instead we grep the output for "error".
           | 
           | Any my favorite reply:
           | 
           | > Is this a rust implementation of autoconf?
           | 
           | https://mobile.twitter.com/m_ou_se/status/136863270144881869.
           | ..
        
       | tux3 wrote:
       | Another fun one (and an easter egg):                   fn main()
       | { break rust; }
       | 
       | This breaks rust =)
        
         | linkdd wrote:
         | My favorite is the punch card example.
        
           | Lvl999Noob wrote:
           | How does that one work?
        
             | steveklabnik wrote:
             | fn punch_card() -> impl std::fmt::Debug {
             | 
             | This is a function that returns some type that implements
             | Debug. Not specifically named.
             | 
             | The rest of it is a combination of two syntaxes: .. and ..=
             | 
             | These are both ranges. The former exclusive, the latter
             | inclusive. You can write something like 1..5 (or 1..=5) to
             | get a range from 1 to 5 inclusive/exclusive, but you can
             | also leave the start or end off: 2.. will give you from 1
             | till the end, ..=5 will give you from the start to 4. This
             | means that .. will give you the full range. It looks more
             | normal when used in context:                   fn main() {
             | let s = "some string";             let some = &s[..=4]; //
             | you'd probably write ..5 but i wanted to show both syntaxes
             | let string = &s[5..];             let all = &s[..]; // a
             | no-op here but useful if s were a String and you wanted
             | 'all' to be a &str                      println!("{}",
             | some);             println!("{}", string);         }
             | 
             | prints "some" and then "string".
             | 
             | So, here's the tricky bit: you can overload what the range
             | is over. They're not just for numbers. So:
             | fn main() {             let mut string = 'a'..='z';
             | let a = string.next();             let b = string.next();
             | println!("{:?}", a);             println!("{:?}", b);
             | }
             | 
             | prints "Some('a')" and then "Some('b')", because iterators
             | return Options.
             | 
             | So... all of this means that, you can implement a range of
             | ranges. And so this string builds up a ridiculous unbounded
             | range of unbounded range of unbounded range of ...
        
       | lmkg wrote:
       | I'm shocked that this doesn't have my own personal favorite:
       | fn eject() -> i32 {         return return return return return
       | return!!!!!!!!11;       }
        
         | woah wrote:
         | What is this?
        
           | pitaj wrote:
           | `!` is binary negation for integers. `return x` is an
           | expression that can be used in place of any type because it
           | will never be used.
        
             | masklinn wrote:
             | > `return x` is an expression that can be used in place of
             | any type because it will never be used.
             | 
             | To clarify: the expression will run, but it has the type
             | `!` ("never"). And while that's still not a "proper" rust
             | type (other uninhabited types are though they may not be
             | quite as special-cased in the compiler), it is in essence a
             | bottom type: since it can't have a value, it will unify
             | with any type.
             | 
             | That's why Rust is perfectly happy with
             | let foo = if a { 1 } else { return 42 };
             | 
             | The first branch has type `{integer}`, the second branch
             | has type `!`. `!` unifies just fine with `{integer}`,
             | therefore the typechecker is happy.
        
               | OJFord wrote:
               | Whaat, was this new in 2018 ed? I could've sworn it was
               | `()` and so you couldn't do that. I don't write rust that
               | often, and happen to be reading this just as I've written
               | some code that's.. not that messy but not that I'm
               | pleased with, wrangling around exactly this sort of if-
               | assign-else-return pattern.
        
               | orthoxerox wrote:
               | () is a unit type, it has a single possible value. ! has
               | _no_ possible values.
        
               | steveklabnik wrote:
               | I don't believe it should be new, but this can happen by
               | accident sometimes. Like, if you included a ; on the non-
               | return branch it would return () overall and you probably
               | thought the return did it when it wasn't that. Hard to
               | say!
        
       | caturopath wrote:
       | What's weird about `fn evil_lincoln() { let _evil =
       | println!("lincoln"); }`?
        
         | steveklabnik wrote:
         | https://news.ycombinator.com/item?id=27154557
        
           | caturopath wrote:
           | Thanks a bunch!
        
           | rabidferret wrote:
           | I have always wondered this!
        
       | steveklabnik wrote:
       | Four years ago I wrote about my favorite example in this file:
       | https://news.ycombinator.com/item?id=15495027
       | 
       | Some things only make sense if you know very old Rust, and
       | they've just been updated as the language has been updated,
       | obfuscating their original meaning.
        
       | bruce343434 wrote:
       | So, semantically, what is `f(return)`? Are these examples
       | supposed to actually do anything, or mirror actual code
       | situations? Or is it just putting a monkey in front of a
       | typewriter?
        
         | chrismorgan wrote:
         | In Rust, just about everything's an expression. `return` is an
         | expression that diverges, meaning it's of type _never_ (spelled
         | !, and coercible to any type, since you know the value will
         | never actually be used).
         | 
         | Here are another couple of ways of writing the same general
         | concept:                 f({           return;           ()  //
         | () in this case because f takes a parameter of that type
         | });            let x = return;       f(x);
         | 
         | So f() will never actually be called. You'll get an
         | unreachable_code warning on f.
         | 
         | As a minimal case it's mostly just funny, but the underlying
         | _concept_ does find its way into real Rust code, mostly in
         | generated code (such as with macros). Also, although just about
         | everything's supposed to be an expression, sometimes things
         | that can be an expression or a statement go a little bit funny,
         | and return is a prime candidate for things going wrong in weird
         | ways in the compiler frontend or in code generation or
         | something, so it's worthwhile having such tests (there will be
         | more involved tests of return specifically elsewhere).
        
           | brundolf wrote:
           | I always thought return, like assignment, could only be a
           | statement and not an expression
        
             | steveklabnik wrote:
             | https://doc.rust-
             | lang.org/stable/reference/expressions/retur...
             | 
             | They're always expressions in Rust, though they are often
             | written with ; to make an expression statement.
        
               | chrismorgan wrote:
               | See also https://doc.rust-
               | lang.org/stable/reference/statements.html on what
               | statements are.
               | 
               | Basically, the only thing that is a statement and can't
               | be an expression is `let _pattern_ = _expression_ ;`.
        
               | estebank wrote:
               | And even _that_ will likely change in the future, letting
               | you write things like                   if some_bool &&
               | let pat = expr {}
        
           | skohan wrote:
           | So does this mean if I call `f(return)`, it will return from
           | the calling context without ever calling the function `f`?
        
             | ben0x539 wrote:
             | yes, it's roughly comparable to                   {
             | let x = return;             f(x)         }
             | 
             | where the call to f is clearly happening after an
             | unconditional return.
             | 
             | A _slightly_ more practical example might be something like
             | f(if blah() { 42 } else { return })
             | 
             | which works out to, roughly,                   {
             | let x;             if blah() {                  x = 42;
             | } else {                 return;             }
             | f(x)         }
             | 
             | To get realistic, if you write                   f(g()?)
             | 
             | you basically get                   f(match g() {
             | Ok(x) => x,             Err(e) => return Err(e),         })
             | 
             | which is how rust does error propagation without exceptions
             | and without constantly repeating a golang-style mantra.
        
               | chrismorgan wrote:
               | Building on the more practical example, this sort of
               | conditional early return is extremely common in Rust,
               | because expression-orientation makes it very pleasant to
               | use. Here's a sketch of some of the sorts of ways it can
               | happen in state machine:                 loop {
               | // ...           state = match state {               A =>
               | B,  // normal               B => break,  // exits the
               | loop               C => { ...; continue; },  // does
               | something and then returns to the start of the loop,
               | skipping setting the state and anything else
               | D => return,  // exits the whole thing               E =>
               | f()?,  // if f() returns an Err or None or similar, it'll
               | return early           };           // ...       }
        
       | tanaypingalkar wrote:
       | i think this = #[]; is for rust analyser, not sure , can anyone
       | tell me what is this #[];
        
         | steveklabnik wrote:
         | It is called an "attribute" and it is part of the language
         | itself.
         | 
         | There is also #![]. The difference is what they apply to, #[]
         | applies to the following thing, #![] applies to the parent
         | thing. You'll see #![] to enable nightly features in Rust, for
         | example, and #[] for things like custom derives.
        
       | Zababa wrote:
       | I'd like to add the "Bastion of the Turbofish", which is my
       | personal favorite: https://github.com/rust-
       | lang/rust/blob/master/src/test/ui/ba...
        
         | iudqnolq wrote:
         | To explain this, since it took me a minute:
         | 
         | Rust has a syntax called "turbofish" for disambiguating
         | ambiguous generic calls. (Coined by Anna Harren on twitter, the
         | first reply is an illustration from Karen Rustad Tolva, the
         | inventor of Ferris [1]).
         | 
         | Although Vec is generic over any item type, you can normally
         | just write                   let foo = Vec::new();
         | 
         | If you're doing something complicated where the compiler can't
         | infer the type, you instead use the turbofish syntax
         | let foo = Vec::<u32>::new();
         | 
         | Some people hate this syntax. One of the better arguments I've
         | seen against it comes (I think) from ManishEarth, who points
         | out that it's very hard for the compiler to detect that
         | incorrect attempts at writing a turbofish are in fact attempts
         | at the turbofish and offer help.
         | 
         | That test case (minus some excellent poetry) is
         | let (oh, woe, is, me) = ("the", "Turbofish", "remains",
         | "undefeated");         let _: (bool, bool) = (oh<woe, is>(me));
         | 
         | That is actually comparing the string "the" against the string
         | "Turbofish" and the string "remains" against the string
         | "undefeated". The test case is there to point out that if the
         | "::" in the turbofish is removed (Vec<u32>::new() instead of
         | Vec::<u32>::new()) there will be cases where it's ambiguous.
         | 
         | [1]
         | https://twitter.com/whoisaldeka/status/914914008225816576?la...
         | 
         | EDIT: Fix my misspelling of ManishEarth
        
           | IainIreland wrote:
           | For the record, I suspect "Manis Heath" is in fact
           | "ManishEarth", aka Manish Goregaokar.
        
             | iudqnolq wrote:
             | Thank you! Fixed
        
               | OJFord wrote:
               | If it helps, for _years_ I read  'dang' as just a fun
               | internet alias, as in Dang son, that's embarrassing.
               | 
               | It wasn't until that NYT (or whichever paper) interview
               | with 'Dan G[...] HN moderator' that it clicked.
               | 
               | (I still frequently hear it wrong in my head. Habits..)
        
           | btown wrote:
           | Oh gosh - I was so primed by the poem to see <woe, is> as a
           | generic specifier (which I thought should fail, because "oh"
           | isn't callable)... that I completely forgot that < is a less
           | than operator and > is a greater than operator. This
           | literally compiles as two string comparisons. I need to go
           | home and rethink my life.
        
             | steveklabnik wrote:
             | The grammar conspires against your brain; when used as
             | comparisons, we tend to include spaces around them, and as
             | generics, we don't. But it's not _required_ for either of
             | those to be that way, with the whitespace. Tricky!
        
           | eslaught wrote:
           | See also this comment that makes the ambiguity explicit:
           | 
           | https://github.com/rust-
           | lang/rfcs/pull/2527#issuecomment-414...
        
       | galangalalgol wrote:
       | I really want to like rust but it seems so very complicated. C++
       | is too but I have been growing with it. It has been my primary
       | language for 27 years.
        
         | linkdd wrote:
         | While I agree that Rust is complicated, this example from the
         | test suite is really a bad example because it's there to test
         | the edge cases of the grammar to ensure backward compatibility.
        
         | mbStavola wrote:
         | Pick any language and I'm sure someone can point you to a test
         | suite like this, it's not really indicative of a complexity
         | problem in Rust.
        
         | kortex wrote:
         | I learned in this order: C, "C++" (very C-like, just enough to
         | write arduino stuff), rust, and the "real" C++14.
         | 
         | Unless you write C++ like C with classes, which is not really
         | writing C++ at all, Rust is actually simpler, easier to learn
         | on your own, more consistent, with better compiler error
         | messages, tooling, documentation, etc.
         | 
         | Your situation - boiling frog analogy - is a very common case
         | but also the only one in which case C++ feels less complicated,
         | simply because it's familiar.
         | 
         | Everything about C++ feels overwhelming, with so many decisions
         | and degrees of freedom at every step, from build system, to
         | lifetime management, mutable state, concurrency, and threading,
         | to libraries, to dependencies, to deployment. It often feels
         | easier, especially for beginners, because there are so many
         | ways to silently do the wrong or suboptimal thing.
        
         | rkangel wrote:
         | One thing that people say is that "Rust frontloads your
         | problems". C++ might be a bit easier to get your head around to
         | start with, but as the complexity of what you're building grows
         | you will discover the 'joy' of debugging segfaults and race-
         | conditions.
         | 
         | Rust has some type-system complexity to get your head around.
         | It's not as painful as it looks (when you're just reading it
         | rather than trying to do it) but there is a hump of
         | understanding there that you have to get over. Once you've done
         | that though, Rust then helps you so much more in dealing with
         | the 'long tail' of difficult problems.
         | 
         | Another way of looking at it is that there are a load of things
         | that you can silently get wrong in C++. There are good
         | practices that help you not screw up e.g. you have to learn how
         | to think about who has pointers to what and how long they live.
         | Rust _forces_ you to think about that stuff from day 1 - the
         | compiler requires you to prove to it that everything is good.
         | It 's a bit painful to start with, but it's a good thing
         | overall.
        
         | smoldesu wrote:
         | If you've been working with C++ for over 27 years, you owe it
         | to yourself to at least try Rust. A lot of the features in it
         | are designed to ergonomically operate around the limitations of
         | compiled languages, while also encouraging the developer to
         | write "correct" code. If you've got some spare time, I
         | wholeheartedly encourage giving it a try!
        
         | pornel wrote:
         | This is not the same kind of complexity. Rust mostly reflects
         | complexity of its domain (thread-safe, memory-safe, low-level
         | without runtime, a lot of correctness enforced at compile-
         | time), rather than unfixable baggage of C and C++'s early
         | design decisions.
         | 
         | I'm not saying all the complexity of C++ is its own fault, but
         | Rust shows how much of it is unnecessary. It manages without
         | any constructors at all (think how many rules and features are
         | connected to them!), without inheritance, and with only _one_
         | (1!) way to initialize a variable.
        
         | larschdk wrote:
         | I often feel the same, and there is certainly a bit of learning
         | curve, but in my experience, all the time you spend battling
         | the compiler in Rust is time saved from debugging in C++,
         | several times over. I'm repeatedly surprised by how my code
         | just works when it finally compiles. It's like the compiler is
         | telling you, "you just made a bug".
        
           | ncmncm wrote:
           | If you _ever_ find yourself spending time  "debugging in
           | C++", you are Doing It Wrong. Coding modern C++ right is the
           | same, that way, as coding Rust: when it compiles, it works.
           | 
           | (In general, if you find yourself inventing falsehoods about
           | other languages to promote Rust, you are Doing That Wrong,
           | too.)
           | 
           | I have personally spent more time, summed over the past
           | decade, preparing bug reports against Gcc than in debugging
           | C++ memory-usage faults.
           | 
           | But C++ compiles faster.
           | 
           | Coding Rust is fun in much the way that Forth is: it is a
           | charge to figure out a way to achieve a thing; you know it
           | will ultimately be possible, and when you find the way, it is
           | an ego boost. You could say, "Rust is the Second Programming
           | Religion", and who could ever _honestly_ disagree?
           | 
           | (But watch extremists downvote this to oblivion anyway.)
        
             | nix0n wrote:
             | > Coding modern C++ right[...]: when it compiles, it works
             | 
             | I don't believe this, but I'd love to be wrong. Is there an
             | exemplary codebase somewhere that I can take a look at?
        
               | shakow wrote:
               | I can give you an example I used a few months ago:
               | struct Shape {           Shape() { init(); };
               | void init() { reset(); };           virtual void reset()
               | = 0;         };              struct Point: Shape {
               | virtual void reset() { _x = _y = 0; }           double
               | _x, _y;         };                   int main() {
               | Point p; // KA-BOOM         }
               | 
               | Compiles without any warning with -Wall & -Wpedantic,
               | fails at run.
        
               | ncmncm wrote:
               | Thank you, this is an example of '90s-style C++, serving
               | as a nice contrast to modern C++. Nowadays we would write
               | struct Point {         double x{}, y{};  // zero-
               | initialization       };            int main() {
               | Point p;  // ok, {0.0, 0.0}         ...         p =
               | Point{}; // no need for a "reset"       }
               | 
               | It has been literal decades since anyone competent would
               | have made a Point derived with virtuals, or have written
               | so much code to achieve so little. (You might see stuff
               | like that at Google.) You can write bad code in any
               | language, but if you have to do extra work to make bad
               | code, it is not tempting.
        
               | shakow wrote:
               | > if you have to do extra work
               | 
               | I would argue that (i) old-fashioned code is not
               | deliberately going out of your way to write bad code, and
               | (ii) this should, at the very least, trigger a warning
               | from the compiler.
        
               | ncmncm wrote:
               | Old-fashioned code is not necessarily _bad code_ ,
               | although the example was; but the topic was "modern C++",
               | not "old-fashioned C++".
               | 
               | Anytime you do all the extra work to write old-fashioned
               | code, you have earned the outcome you get. The oldest-
               | fashioned code looks just like C, which you _can_ still
               | write in a C++ program, _if you want to_. But there is no
               | reasonable temptation to. Good modern code is equally
               | fast, often faster, and more easily written, understood,
               | and maintained.
        
               | kelnos wrote:
               | I haven't done any serious C++ in about 15 years.
               | 
               | If I were to try to pick it up again today, I would have
               | to learn "modern C++"[0] incrementally. I would still
               | have some old habits, and they would take time to iron
               | out. My guess is that it would take me at least a year,
               | possibly more, to become fully proficient in "modern
               | C++".
               | 
               | And even then, I'd still expect that I'd occasionally
               | write some C++ in the "old" way. Maybe I'm tired and
               | forget, maybe I'm lazy and want a shortcut. Who knows.
               | The compiler won't save me from my "old C++". It'll be
               | there, warts and footguns and all.
               | 
               | I'd much rather write in a language designed to not have
               | these problems in the first place, and let the compiler
               | catch as many problems as it can.
               | 
               | [0] Whatever "modern C++" means; I suspect current C++
               | developers can reasonably disagree on the details, as has
               | been the case for the entire history of C++.
        
               | linkdd wrote:
               | Modern C++ is what was in the Boost framework 15 years
               | ago.</troll>
               | 
               | Joke aside, the STL grew in complexity and features to
               | provide more compile-time constructs and to integrate
               | more functional programming aspects.
               | 
               | For example: I've been told many times that in "modern
               | C++" you don't need `new` nor `delete`.
               | 
               | There are smart pointers, std::array, std::optional,
               | const-expressions, you'll find more and more "single-
               | header" libraries (for JSON, an either monad, etc...),
               | even modules[1].
               | 
               | It's like learning a new language. C++11, 14, 17 and 20
               | are completely different to C++03 while remaining
               | backward compatible (a critical feature for C/C++
               | languages it seems).
               | 
               | [1] - https://en.cppreference.com/w/cpp/language/modules
        
               | shakow wrote:
               | > but the topic was "modern C++", not "old-fashioned
               | C++".
               | 
               | The problem being that there is no definition of "modern"
               | C++, and even less so that would be enforced by
               | compilers.
               | 
               | > Anytime you do all the extra work
               | 
               | There is no extra work to write "old-fashioned" code.
               | Quite the opposite actually; one has to indicate to
               | compilers to accept newer features rather than the
               | opposite.
               | 
               | > Good modern code
               | 
               | This is a very flimsy, handy-wavy concept, that changes
               | drastically from one "best practice" guide to the other.
        
               | nix0n wrote:
               | You've misunderstood me, "ncmncm" has claimed that a
               | "right" way to code C++ exists. I would like to see such
               | a codebase, if one exists.
               | 
               | Clearly, this isn't it, but I do appreciate the
               | interesting snippet. Would the compiler have caught this
               | if `Shape()` called `reset()` directly?
        
             | kortex wrote:
             | > Coding modern C++ right is the same, that way, as coding
             | Rust: when it compiles, it works.
             | 
             | Works in what way? Serious question. Do you mean "works" as
             | in 1) "doesn't crash/seg/overflow" or 2) "doesn't UB" or 3)
             | "doesn't have race bugs" or 4) "does what the programmer
             | intended"?
             | 
             | I would say from my experience, none are strictly true.
             | I'll hazard you mean to imply the dev knows the language
             | pretty well in order to satisfy "works lvl 1" but you still
             | have veterans running into bugs of the 2-4 variety.
             | 
             | But even to get to (1), C++ requires a _ton_ of domain
             | knowledge. I 've been working with C++ intensively for
             | several months and incidentally for years, and I still seg
             | every now and then. Even after using Valgrind, it _feels_
             | rickety. Meanwhile, I 've spent a few dozen weekends on
             | Rust and I just feel way more confidence that my program
             | will "just work" without crashing.
             | 
             | That's all still "level 1 works". When it comes to race
             | conditions and programmer intent making its way into
             | correct code, it's no comparison. Rust is way easier to get
             | my intent into a running program. C++, I still have to lean
             | into logs and debugging, because it just doesn't quite do
             | what I want a non-trivial amount of the time.
             | 
             | It's not a religion. Rust simply is a better experience. I
             | can say that having learned both basically side-by-side.
        
             | skohan wrote:
             | > it is a charge to figure out a way to achieve a thing;
             | you know it will ultimately be possible, and when you find
             | the way, it is an ego boost.
             | 
             | This is actually something I agree with, even as a fan of
             | Rust. Sometimes I wonder to what extent the appeal of Rust
             | is based in the fact that you get to feel like a CS
             | undergrad again, climbing mountains to make code compile
             | and run
        
             | emtel wrote:
             | The only way this is even close to true, imo, is you have
             | really good tests and have taken the time to get those
             | tests running with every available sanitizer, including
             | MemorySanitizer (which is a huge pain).
             | 
             | And even then, I think you're still behind rust. Sanitizers
             | can only catch data races / memory errors / uninitialized
             | reads / etc that actually happen. OTOH, Rust _proves_ that
             | your program doesn't have these faults (modulo unsafe
             | blocks).
        
               | ncmncm wrote:
               | If you are relying on testing for correctness, you have
               | already lost. In any language.
               | 
               | As Dijkstra noted, testing can only prove a program
               | wrong. The way to get correct programs, in any
               | expressive-enough language, is by construction. At each
               | level, do only operations that are well-defined by the
               | level below. Expose only well-defined operations to the
               | next level up. The compiler proves that the types are
               | used correctly; so, when coding a library, you put the
               | type system to work to make wrong code harder than
               | correct code.
               | 
               | That is the right way to code Rust, too.
        
             | linkdd wrote:
             | It was my understanding that the memory model of Rust is
             | inherently different than C++'s.
             | 
             | Genuine noob question: can you really compare Rust's borrow
             | checker and lifetime management to the smart pointers in
             | C++?
        
               | steveklabnik wrote:
               | You can compare them, but the comparison won't show that
               | they're equal :p
               | 
               | C++'s smart pointers have direct analogues in Rust,
               | though there are some differences. (unique_ptr<T> and
               | Box<T>, shared_ptr<T> and Rc<T>/Arc<T>)
               | 
               | The borrow checker is something else completely. The
               | closest analogue in C++ is the https://isocpp.github.io/C
               | ppCoreGuidelines/CppCoreGuidelines, which offer _some_
               | similar kinds of checks to Rust, but don 't attempt to go
               | nearly as far as Rust does.
        
               | ncmncm wrote:
               | There is nothing similar between Rust's borrow checker
               | and anything in C++.
               | 
               | In modern C++ you get compile-time correctness by
               | construction: you do things that reduce to correct code.
               | All of the low-level mistakes remain possible, but are
               | not tempting. Low-level, C-like operations are like
               | writing "unsafe" in Rust; you can if you want to, or need
               | to, but they are uglier, and anyway almost always not
               | needed. Just as when coding a Rust "unsafe" block: if it
               | ever is, you use extra care.
               | 
               | In a very real sense, C++ libraries perform in the role
               | of the Rust compiler by insulating you from operations
               | that are risky. The operations a good library exposes are
               | safe. A Rust compiler bug could expose you to a memory
               | fault, in the same way that a C++ library bug could; but
               | the Rust compiler is well exercised and tested, much as
               | is is a mature library. You need libraries anyway.
        
               | galangalalgol wrote:
               | Thats fine until "that guy" grabs the raw pointer out of
               | a smart pointer and deletes it, or c style casts an
               | interface to his version of the interface because he
               | wanted it to return an int instead of a double. Then
               | either pushes straight to dev or rounds up similarly evil
               | individuals to approve the merge. I have debugged those
               | errors. You can talk about accountability but even if you
               | manage to get rid of one such dev there always seem to be
               | more. And modern C++ doesn't really have great tools for
               | thread safety. Its easier to get right than in 98, but
               | still not hard to get wrong.
        
               | estebank wrote:
               | Genuine question, is iterator invalidation still a
               | problem that people can hit with modern C++ constructs? A
               | quick search seems to answer "yes"[1], but I'm not
               | involved in writing C++ to evaluate what the common
               | mitigations are.
               | 
               | For what is worth, I believe that 99% of what you can
               | express in one language you can in the other. If you're
               | hitting walls with the borrow checker when writing Rust
               | you always have the options of either using the
               | equivalents of unique_ptr<T> and shared_ptr<T>, or of
               | cloning memory.
               | 
               | > A Rust compiler bug could expose you to a memory fault,
               | in the same way that a C++ library bug could; but the
               | compiler is well exercised and tested, much as is is a
               | mature library.
               | 
               | I'm not sure what this sentence was expressing, but it
               | seems you're implying here that rustc isn't well
               | exercised and tested?
               | 
               | [1]: https://medium.com/@lightcone/iterator-invalidation-
               | in-moder...
        
               | ncmncm wrote:
               | Iterator invalidation, like pointer invalidation, remains
               | a thing. Iterators and pointers are normally treated as
               | ephemeral, except where guarantees are provided.
               | 
               | It is hard for me to imagine how " _the compiler is well
               | exercised and tested_ " could be perceived to mean the
               | opposite. Explain?
        
               | estebank wrote:
               | > It is hard for me to imagine how "the compiler is well
               | exercised and tested" could be perceived to mean the
               | opposite. Explain?
               | 
               | I misread it as talking about modern c++ compilers and
               | libraries being well exercised and tested, and
               | _contrasting that_ with rustc.
        
               | kelnos wrote:
               | I think you're fundamentally misunderstanding the
               | objections people have to "modern C++" when compared to
               | Rust.
               | 
               | You say that it's not "tempting" to use less-safe
               | constructs in C++ these days, but that's not the point.
               | You are assuming a perfect C++ developer who never makes
               | mistakes and always knows to use the proper, modern
               | constructs, and will properly document any time that they
               | absolutely must use a less-safe construct, and that
               | documentation will never drift or go out of date.
               | 
               | That developer does not exist in any meaningful or useful
               | way. Developers make mistakes. Developers don't always
               | have completely up-to-date knowledge or understanding of
               | what the correct, "modern C++" way is to do literally
               | everything. A developer who genuinely does need to do
               | something less safe may or may not document it, and
               | anyone who comes by later and changes the code may not
               | update the documentation accordingly.
               | 
               | The Rust compiler will not allow you to do unsafe things
               | (absent bugs in rustc); attempting to do so is a compiler
               | error. If you really must do unsafe things, you must
               | surround that code in an unsafe block, which serves as
               | documentation that must be kept up to date, or the code
               | will not compile. Others can inspect your code and very
               | easily decide if they want to use it based on how much
               | unsafe code they are willing to accept.
               | 
               | I don't want a compiler that will only ensure certain
               | kinds of correctness if I already know how to write
               | things in the "correct way" (which has traditionally been
               | a moving target in the C++ world). I want a compiler that
               | will ensure those types of correctness always, and reject
               | programs where it can't give me that guarantee. The C++
               | compiler will not do that, but the Rust compiler will.
               | 
               | > _In modern C++ you get compile-time correctness by
               | construction_
               | 
               | No you don't! You only get this if you've written your
               | code correctly! The entire point of "correct by
               | construction, verified by the compiler" is that you
               | cannot write the code in an incorrect way that the
               | compiler will accept. And the C++ compiler absolutely
               | will accept "old-school C++" that could be incorrect. Put
               | another way, there is no C++ compiler that will only
               | accept "modern C++" and reject "old-school C++". And even
               | if someone were to try to write such a thing (I am
               | skeptical it's possible), reasonable, knowledgeable C++
               | developers could easily disagree on what should and
               | shouldn't be included.
        
           | skohan wrote:
           | Just to give an alternate viewpoint, I'm currently writing
           | most of my code in Rust, and while I am generally a fan of
           | the language, and a _huge_ fan of the tooling and community,
           | I think Rust often gets a pass on complexity because it 's
           | mostly compared against C++ which is itself a monstrosity.
           | There are many, many other languages to which Rust seems
           | insanely complex and difficult to program in by comparison. I
           | think that often gets blamed on accommodating the borrow
           | checker, and Rust's low-level nature, but I think a lot of it
           | is avoidable.
        
             | steveklabnik wrote:
             | I'm curious, not saying you're right or wrong: what bits do
             | you think are avoidable?
             | 
             | Personally, I agree that Rust is complex, but almost all of
             | that complexity is inherent to the kinds of tradeoffs Rust
             | makes. With different tradeoffs, I can certainly imagine a
             | much simpler language, but it's less clear to me what stuff
             | could be significantly simplified without doing so.
             | However, I am extremely biased!
        
               | skohan wrote:
               | My go-to example would be the module system:
               | 
               | 1. 90% of the time, rust module layouts are basically a
               | copy of the file-system heirarchy, so why do I have to
               | type this? This system would be much more approachable if
               | it gave the file-system hierarchy as the default module
               | layout, and let you override it explicitly. The argument
               | I have heard against this is that some people like to
               | comment out module declarations during debugging. I don't
               | find this compelling, because I don't see why you
               | couldn't have an explicit way to ignore a module instead.
               | This is just one of many cases where Rust seems to
               | prioritize the edge case at the expense of the common
               | case.
               | 
               | 2. On top of requiring a lot of boilerplate the module
               | system is quite esoteric. I've had to learn it twice: a
               | few years ago I was dabbling in Rust, and I remember
               | having to struggle a bit to understand how to add a
               | second file to my project, and then about a year ago when
               | I was getting back into rust I found it unintuitive a
               | second time. I challenge you to find someone who is
               | unfamiliar with the module system, and see how long it
               | takes them, using _only_ the documentation, to figure out
               | where they have to put the `mod` declarations to add
               | nested submodules to their project to make it compile.
               | Maybe I am unreasonably thick, but I don 't think it's my
               | problem since I've worked with a lot of languages, and
               | never had this much trouble.
               | 
               | 3. This esoteric system isn't even deterministic. Imagine
               | I have the line `mod foo` in my `lib.rs`. Where can I go
               | to find the source for that? Well, it depends. It could
               | be in `src/foo.rs`, or it could be in `src/foo/mod.rs`.
               | And let's say I'm using external crate: `use some_crate`.
               | Which import does that correspond to? It could be:
               | `some_crate`, or it could be `some-crate` in my
               | `Cargo.toml`. You just have to kind of know all of these
               | implicit behaviors of the compiler to know what's going
               | on.
               | 
               | So this is just one feature, but IMO it's just one
               | example of a case where Rust puts very little emphasis on
               | the UX and understandability of the language.
        
               | iudqnolq wrote:
               | I definitely think you're right that the module system is
               | complicated, even the new module system. I would like to
               | make one nit though: it's definitely deterministic.
               | Deterministic just means it can be predicted, not that
               | you specifically can predict it. If the module system was
               | non-deterministic that would be a much bigger issue
        
               | skohan wrote:
               | Ok fair enough, that is true. I guess it would be more
               | accurate to say that a module declaration is ambiguous
               | with respect to the file-system location of the code it's
               | referring to.
        
               | steveklabnik wrote:
               | Cool thanks! This is a great example of my mental bias,
               | actually: I don't tend to think of the module system as a
               | "language feature" even though it clearly is!
               | 
               | There are some good reasons and interesting arguments
               | around all this, but given that I was just curious about
               | your opinion, I won't bore you with all that :) Thanks!
        
               | skohan wrote:
               | No worries, happy to share!
        
               | skoodge wrote:
               | I'll chime in and say that for me at least, the module
               | system was a hurdle at the start and coincidentally the
               | only part of the language where the explanation in The
               | Book (which is excellent, so thank you, btw!) did not
               | click for me.
               | 
               | I think it's debatable whether the module system really
               | is complex or just different from what newcomers are used
               | to (and by now I've grown pretty accustomed to it). But
               | in contrast to most other language features, where it was
               | clear what I was getting in return for the steep learning
               | curve, the module system seemed overly complicated at the
               | time for no real benefit. Not a big issue by any means
               | and I would choose Rust with its module system over the
               | alternatives most days of the week, but it is one
               | tradeoff that to me at least seemed orthogonal to the
               | other borrowing-related complexities.
               | 
               | (What is more worrying to me nowadays is the whole async
               | story. I do hope that some of it will get better once
               | certain features land and it is certainly an area where
               | some additional complexity is unavoidable, but it is the
               | only part of Rust that I dread touching despite heavily
               | using async in a moderately sized personal project due to
               | the need for WASM + IndexedDB, simply because lifetime
               | issues become much more tricky once async and either
               | traits, recursion or closures are involved. By now I am
               | consciously trying to limit any async parts of the
               | program to a simple and stupid "Rust-light" style without
               | any "fancy" features such as traits or closures, which
               | does not feel like a proper solution. So yeah, in general
               | I agree with you: Rust is certainly complex, but for the
               | most part not unnecessarily so.)
        
               | steveklabnik wrote:
               | Thanks!
               | 
               | Yeah, teaching the module system is kind of my white
               | whale. Carol and I have spent more time on that part of
               | the book than almost any other; re-written like five
               | times.
               | 
               | My current working theory is that most people assume that
               | "the module system" is similar to whatever one they've
               | used in the past, then run into problems, and leads to
               | frustration. I've talked to so many people who have
               | totally opposite problems with it, with no real pattern
               | to issues or expectations.
               | 
               | I think that it's very straightforward, personally, with
               | very very simple rules (especially in 2018). But I
               | certainly acknowledge that I am the exception, not the
               | rule.
        
               | orthoxerox wrote:
               | > My current working theory is that most people assume
               | that "the module system" is similar to whatever one
               | they've used in the past, then run into problems, and
               | leads to frustration. I've talked to so many people who
               | have totally opposite problems with it, with no real
               | pattern to issues or expectations.
               | 
               | When I treated modules as Java packages or C# namespaces
               | I hated them. Only when I realized that I can treat them
               | as glorified C imports did they start to make sense. I
               | still hate them, but at least I can rationalize their
               | design now.
        
               | rabidferret wrote:
               | > I think that it's very straightforward, personally,
               | with very very simple rules (especially in 2018).
               | 
               | I agree with you. It's a shame we don't have a simple
               | visual metaphor to describe this
               | 
               | EDIT: Wait does hacker news just delete non-ascii? I
               | tried to put an upside down smiley here how is this even
               | worse than I thought
        
               | steveklabnik wrote:
               | https://news.ycombinator.com/formatdoc is the only actual
               | description, but it does seem that non-ascii text is
               | ignored, yes.
        
               | saagarjha wrote:
               | The rules are mainly there to filter out emoji; it can do
               | most non-ASCII outside of that.
               | L[?][?][?]i[?][?][?]k[?][?][?]e[?][?][?]
               | Z[?][?][?]a[?][?][?]lg[?]o[?][?].[?][?]
        
               | estebank wrote:
               | You might be interested in taking a look at and
               | potentially participating in the "Async Vision
               | Document"[1] which is an exercise the team is going
               | through to collect feedback about the current state of
               | the ecosystem and what the pain points are, as well as a
               | way to lay doing what the desired future state of async
               | Rust should be[2]. The process is happening, as you would
               | expect, in the open and there's still time to influence
               | it[3] if your concerns aren't yet addressed or even
               | mentioned[4].
               | 
               | [1]: https://rust-lang.github.io/wg-async-
               | foundations/vision.html
               | 
               | [2]: https://blog.rust-lang.org/2021/03/18/async-vision-
               | doc.html
               | 
               | [3]: https://github.com/rust-lang/wg-async-
               | foundations/pulls
               | 
               | [4]: https://github.com/rust-lang/wg-async-
               | foundations/issues
        
               | bsder wrote:
               | > By now I am consciously trying to limit any async parts
               | of the program to a simple and stupid "Rust-light" style
               | without any "fancy" features such as traits or closures,
               | which does not feel like a proper solution.
               | 
               | I disagree that this is bad, and I think you've made a
               | _good_ decision.
               | 
               | I find that too many Rust programmers reach for a closure
               | _WAY_ too often and, even when they should grab a
               | closure, they make it far too complicated. Closures
               | should be _short_. Inline closures are nice when they 're
               | a single line part of "collect()" (although, I've seen
               | some that make me want to hang the author ...).
               | 
               | However, if your closure is 15 lines long invoking a
               | builder chain (this is a common issue in EventLoop type
               | closures), that should be a _function_. This is before I
               | get started about how builder chains are a gigantic
               | misfeature to paper over the fact that the language doesn
               | 't have default/named function arguments.
               | 
               | Anyway ... I have found that "make it a named function
               | and call it" is often a far better way to communicate
               | exactly what your intent was.
        
               | skohan wrote:
               | > builder chains are a gigantic misfeature to paper over
               | the fact that the language doesn't have default/named
               | function arguments.
               | 
               | Totally agree. I think it's telling that Rust Analyzer
               | basically inserts argument labels inline in the editor,
               | and this is the preferred way to work with Rust.
        
       | doggodaddo78 wrote:
       | Good god. Wicked, bad, naughty Rust. Oh, it is a naughty
       | language, and it must pay the penalty -- and here in Castle
       | Congruence, we have but one punishment for setting alight the
       | grail-shaped semantics.
        
       | ivan888 wrote:
       | Languages like this: fun to author the grammar, and have the
       | entire thing in your head. So expressive! Difficult for anyone
       | else (including yourself in 2 years) to understand
        
         | steveklabnik wrote:
         | Virtually any language can be "like this," these examples are
         | all nonsense designed to stress the compiler. This code isn't
         | supposed to be understood by any human.
        
       | augustk wrote:
       | "Another lesson we should have learned from the recent past is
       | that the development of richer or more powerful programming
       | languages was a mistake in the sense that these baroque
       | monstrosities, these conglomerations of idiosyncrasies, are
       | really unmanageable, both mechanically and mentally. I see a
       | great future for very systematic and very modest programming
       | languages."
       | 
       | -- "The Humble Programmer"
       | 
       | https://www.cs.utexas.edu/~EWD/transcriptions/EWD03xx/EWD340...
        
         | kubb wrote:
         | I mean the fact that you can do funny stuff with the syntax
         | rules and the type system doesn't disqualify their usefulness.
         | Rust's type system is impressive as it enables enforcing
         | powerful constraints over how the code is allowed to execute,
         | eliminating whole classes of bugs, and enabling elegant, zero-
         | cost abstraction over swathes of algorithms and data
         | structures, which in turn lets you have high quality libraries
         | that provide to-the-point solutions for so many of the common
         | tasks that a developer will deal with day to day.
         | 
         | A more elegant language that solves the problems that Rust
         | solves, with a production-ready implementation and a good
         | ecosystem hasn't been presented to the world yet. It's to be
         | expected that if you want to benefit from its advantages, you
         | need to pay the cost of dealing with a more complex language.
         | On the other hand paying the cost of being exposed to more
         | concepts pays off by giving you more flexibility to craft
         | simple solutions. Complexity of a project can vastly exceed
         | that of the language the project is written it. In that case
         | the cognitive cost of using more advanced tools to manage that
         | complexity is a boon in the long run.
         | 
         | This is analogous to any kind of more sophisticated technology,
         | e.g. you can argue that a scythe is easier to use than a
         | harvester, but with a big enough field, if you have the option,
         | you'll go for the latter.
        
         | pcwalton wrote:
         | Is it complexity, or is it the result of the _simplification_
         | of unifying expressions and statements in most cases?
        
         | shakow wrote:
         | The problem is that the complexity in computing is
         | intrinsically irreducible. Sure, you can get nice nifty little
         | languages, but then the latent complexity will be transferred
         | into the program itself (e.g. program length, cyclomatic
         | complexity, etc.) rather than in the language; that's why
         | although they can technically do the same thing, we have a
         | spectrum of languages ranging from assembly to APL.
         | 
         | Practically, it's exactly what comes out of the infamous Rust
         | vs. Go debate: for instance, the former prefers a short and
         | concise albeit arguably more complex encoding and processing of
         | the errors based on a monadic type, whereas the other leans
         | toward a simpler, but more verbose, double return value +
         | if/else/early return. Chose your poison.
        
         | fanf2 wrote:
         | Dijkstra is best read as food for thought, since he is more
         | often interesting than right.
        
       ___________________________________________________________________
       (page generated 2021-05-14 23:00 UTC)