[HN Gopher] The Rust I wanted had no future
___________________________________________________________________
The Rust I wanted had no future
Author : dochtman
Score : 802 points
Date : 2023-06-05 07:03 UTC (16 hours ago)
(HTM) web link (graydon2.dreamwidth.org)
(TXT) w3m dump (graydon2.dreamwidth.org)
| titzer wrote:
| > Library-defined containers, iteration and smart pointers.
| Containers, iteration and indirect-access operations (along with
| arithmetic) comprise the inner loops of most programs and so
| optimizing their performance is fairly paramount; if you make
| them all do slow dispatch through user code the language will
| never go fast. If user code is involved at all, then, it has to
| be inlined aggressively. The other option, which I wanted, was
| for these to be compiler builtins open-coded at the sites of use,
| rather than library-provided. No "user code" at all (not even
| stdlib). This is how they were originally in Rust: vec and str
| operations were all emitted by special-case code in the compiler.
| There's a huge argument here over costs to language complexity
| and expressivity and it's too much to relitigate here, but .. I
| lost this one and I still mostly disagree with the outcome.
|
| Trust me, you do _not_ want to put this stuff into the compiler.
| It 's not _just_ that it 's cheating for perf, but it's confusing
| to users (no source code to read how these work) and frustrating
| that they can't write their own. Ultimately what this really
| means is that these things are indeed written in _some_ language
| --the compiler 's _IR_. JavaScript actually has a ton of this
| kind of things and every JS engine has gone through multiple
| generations of "what language do we write Array.sort in!?". In
| V8, these intrinsics are written in a DSL because doing them in
| asm, a special dialect of C++, or one of two compiler IRs ended
| up being more trouble than its worth.
|
| You want a clear separation between what is language and what is
| library.
| marcosdumay wrote:
| Almost all high-level languages have builtin containers. I do
| think it's a bad fit for Rust (at least for what Rust became),
| but it's not the end of the world by itself.
|
| The fact that JS has about 3 of them (is it only 3? I'm not
| sure of this), with different interfaces, and non-usual
| behavior is what creates problem there. But other languages
| quite successfully make their own containers either different
| enough or similar enough that it's not a problem.
| titzer wrote:
| > The fact that JS has about 3 of them (is it only 3? I'm not
| sure of this), with different interfaces, and non-usual
| behavior is what creates problem there.
|
| V8 has over 35,000 lines of Torque, the DSL for describing
| these kinds of built-ins. Big chunks of that are dealing with
| Array methods like splice, sort, from, foreach, etc; and then
| there is stuff like BigInt, arguments object, a zillion
| methods on strings.
|
| > Almost all high-level languages have builtin containers.
|
| I don't think that's really true. Maybe for Python and Ruby,
| and other dynamically typed languages. I dunno about Swift.
| But it's not really true for Java or C#. Those languages have
| their collection/containers libraries written in themselves.
| Java has _other_ problems separating the language from the
| libraries, but it 's not due to collections.
| paulddraper wrote:
| Array
|
| Map
|
| Set
|
| WeakMap
| dexwiz wrote:
| TypedArrays are also their own thing. But unless you are
| reading binary files/WASM, most JS devs don't use them.
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Guid...
| dermesser wrote:
| I read this as wanting a different "feeling" for the language.
| Go uses this approach, with maps and arrays being baked into
| the syntax, and things work just fine. Implementing Vec in user
| code is rather the C++ "feeling".
|
| Personally I'm happy Rust took the C++ route, it makes things
| more interesting, but I can see his point.
| smitty1e wrote:
| [flagged]
| camgunz wrote:
| I would love to play with the language Graydon describes here.
| Aissen wrote:
| On integer wrapping, I think explicit wrap is annoying at first,
| but eliminates a whole class of bug. I can only agree with:
|
| > (Swift at least traps in release by default -- I wish Rust had
| chosen to).
|
| I enable it in release on serious projects:
| [profile.release] overflow-checks = true
| wongarsu wrote:
| At least Rust has left the door open to allow panicking on
| overflow even in release builds in the future - presumably on
| platforms where hardware support makes this cheap enough.
| jedisct1 wrote:
| And then you may just have introduced side channels in crypto
| code.
| Aissen wrote:
| > And then you may just have introduced side channels in
| crypto code.
|
| _incorrect_ crypto code. If overflow is intentional, it
| should be annotated as such in operations, will generate
| similar assembly and won 't panic. If it isn't intentional,
| then the code was bad to begin with.
| dooglius wrote:
| If the branch can be mispredicted, and the misprediction
| happens depending on internal state you don't want to leak
| (cf. Spectre), then you have a side channel even if the
| branch is never actually taken.
| layer8 wrote:
| The correct (but hard) way to prevent overflow-related bugs
| would be to insist on the compiler being able to prove that no
| overflow can occur. Basically the same thing you do in your
| head to convince yourself that the program is correct and won't
| overflow, only in a more formally rigorous fashion. Modulo
| semantics by itself doesn't prevent bugs.
| the_duke wrote:
| Rust has an optional clippy lint that will warn/forbid all
| basic arithmetic that might over/underflow, forcing you to
| use dedicated methods with explicit behaviour instead.
|
| It's very annoying , but I use it in code where this is
| critical.
| dathinab wrote:
| One thing I want to add wrt. "Cross-crate inlining and
| monomorphization. I wanted crates to allow inlining inside but
| present stable entrypoints to the outside." [..] is that it was
| in general well desired AFIK by most developers but so far out of
| scope that you probably would need to had the resources rust has
| today _before_ the 1.0 release to get it done right. At lest with
| the state CS research had been at during that time. By now due to
| various reasons (e.g. swift) there is much more research in that
| direction already done hence a new language has it much easier.
| estebank wrote:
| I think this is something that Rust might try again at some
| point, as part of a stable rust to rust ABI.
| dathinab wrote:
| The problem is that it's not that simple to make it work
| nicely you need to design you language around such
| constraints. Trying to do it the other way around is even
| harder.
|
| For example rust in it's current design relies quite a bit on
| the combo of: generics monomorphisation + inlining + dead
| code elimination. But this reliance doesn't play well with an
| stable ABI. Similar features like generic associated types
| and similar do not make the story easier. Like an rust stable
| ABI likely would only support `dyn Trait` and no generics or
| "on the fly" provide a `dyn` variant for generic method where
| possible. But even that isn't really good enough for a lot of
| use cases in a lot of different ways. Additionally a ton of
| important rust things are not `dyn` compatible at all.
|
| Even some of the "easy" to solve things aren't that easy for
| non-technical reasons. E.g. a lot (but not all) of `impl
| Into<T>`, `impl AsRef<T>`, `impl Borrow<T>` etc. cases are
| best handled for a stable ABI context by aplying the single
| function of the trait (`.into()`,`.as_ref()`, etc.) _before_
| calling the function and only having an ABI stable version
| for that. The side of which traits qualify for this is easy
| (you annotated the traits) the "when not apply it even if it
| seems valid" part isn't that easy not for technical reasons
| but for communication/documentation/avoiding unexpected
| outcomes reasons.
| MontagFTB wrote:
| > Not 100% clear about actors -- I was weirdly focused on that
| model that in practice has many issues...
|
| Any ideas what he meant here?
| aidenn0 wrote:
| I wanted nearly the same language Gradyon wanted, but I also
| suspect that my tastes are insufficiently mainstream for a
| language to my tastes to be as popular as Rust has become (much
| less as C++ is).
| garbagecoder wrote:
| >a viable c++ alternative
|
| Wow, so one of the principal creators understands that that's
| what it is, not a replacement for everything under the sun.
|
| And it's a GREAT c++ alternative, but ffs please stop telling me
| to use it for everything from webdev to embedded.
| Keyframe wrote:
| The Rust he wanted sounds a lot like Ada.
| tejinderss wrote:
| Or like ocaml with its module system and no explicit lifetimes
| / & type.
| lawn wrote:
| Which reminds me, I really need to try out ocaml.
| giraffe_lady wrote:
| A really unknown and underrated language for how good it is
| right now is rescript. It's ocaml's type & module systems
| and runtime semantics grafted onto JS. Ocaml's tooling,
| ecosystem, and standard lib situation are all ...fine...
| but quirky in ways that can be barriers when learning it.
|
| Rescript lets you skip most of those, plus the (overblown
| but also real) weird syntax. I like ocaml and use it for
| projects still but if you just want to play around with
| what's unique and strong about it, rescript is what I would
| recommend right now.
| glandium wrote:
| The original rust compiler was written in ... Ocaml.
| qaq wrote:
| Will be interesting to see how things play out in Mojo as it
| looks like they will account for lessons learned by Rust
| community.
| cmrdporcupine wrote:
| When I first read Graydon talking about his plans for Rust, I
| pictured it as StandardML (or OCaml) without a garbage collector,
| and I was sold. This list doesn't look fully like that, but is
| closer than current Rust is.
|
| My interest back then was in a higher level language with type
| inference and a modern ML-like type system but that could be used
| for systems programming, especially in database, virtual machine,
| and even operating system dev.
|
| These days I work pretty much full-time in Rust, and I think Rust
| as it is today delivers on _some_ of that promise, but not all. I
| feel like the language 's borrowing and ownership checking are
| pretty brilliant _but_ really begin to become a pain when dealing
| with nested and interrelated trees of objects and iterators (like
| if building a compiler or query evaluator, etc.), and resorting
| to Arc /Rc/RefCell, etc. feels awkward.
|
| I'm not sure if the language Graydon talks about here would have
| been better for that or not.
|
| But I'm also happy we have Rust, because it's an improvement over
| what else is out there, and I hope the community gets through its
| growing pains.
| javajosh wrote:
| Too bad graydon didn't get his way with build times. I've come to
| believe that the most important feature of any development
| environment is to minimize the built-test-debug cycle. Of course,
| a real system has many different ones, ranging from "language
| level" to "I have to redeploy and perform a complex series of
| actions in an app or 3". But at the language level I've found
| that _build performance_ is of paramount importance. And when a
| project ignores build perf, _everyone suffers every time they
| build_. Since the lower-limit is the language compiler, it should
| therefore be kept very fast. (And the people working on the
| applications must _constantly resist_ adding features that slow
| the BTD loop down any further.)
|
| A good example of this trade-off in Java is Lombok. A very handy
| library that legitimately avoids a ton of boilerplate, but it
| also absolutely tanks your build time. In a real system, a large
| one, your team is better off just getting good enough with their
| editor that they can generate the hateful boilerplate, and leave
| Lombok out. Because you'll be paying for Lombok all the time, and
| only need it a small fraction of the time. There are hundreds,
| thousands of these conveniences that are deeply tempting but
| should be avoided, in every build. The problem is that the
| programmers become attached to these little nicities and actively
| resist giving them up, even though they are so costly.
| zamalek wrote:
| I don't understand the big issue with build times: I have
| always found that they are almost instant for incremental
| builds (assuming you use lld or mold).
| umanwizard wrote:
| > I have always found that they are almost instant for
| incremental builds (assuming you use lld or mold)
|
| They are not almost instant once your project grows to a
| certain size, which is still well within the bounds of a
| realistic single company's project (I work on Materialize
| which is all in rust).
| zamalek wrote:
| And you have it set up with multiple compile units, shared
| generics, and lld (or mold ideally)?
| umanwizard wrote:
| > multiple compile units
|
| Yes
|
| > shared generics
|
| I don't know what that is. Some googling suggests that
| it's a nightly-only feature, whereas we use the stable
| compiler. If it's something that can be done in stable,
| I'd appreciate a pointer.
|
| > lld (or mold ideally) $ echo
| $RUSTFLAGS -C link-arg=-fuse-ld=lld -C
| debuginfo=0
| duped wrote:
| Then you're not working on big enough projects or in
| ecosystems where you need to regularly perform clean
| compiles, because afaik the "incremental" output from
| cargo/rustc isn't relocatable.
| bluGill wrote:
| In C++ a small change to a header can result in having to
| rebuild thousands of different files. Even if each file
| builds fast the total can be long. I have also benchmarked
| including a specific header (not using it, just including it)
| costs .5 seconds which adds up quick in those thousand files.
| Another benchmark found a specific boilerplate code construct
| added .1 seconds to the time to compile the file each time
| you added that one line - and it was a line commonly
| repeating in a header (MOCK_METHOD from gmock - there is a
| FAQ entry on how to get this time down)
| duped wrote:
| On top of this, there's this bogus mantra in the Rust community
| that "if it type checks then it works."
|
| I've spent of hours interactively debugging Rust code, crawling
| through tracing output etc, and hours waiting for Rust code to
| link with mold because I added an eprintln somewhere, so I'm
| not convinced.
|
| I love writing Rust and think it makes my life easier, but
| there's this stockholm syndrome about compile times. It sucks,
| and it's not the type system or borrowchecker's fault.
| mrkeen wrote:
| > there's this stockholm syndrome about compile times. It
| sucks, and it's not the type system or borrowchecker's fault.
|
| What do you mean?
| duped wrote:
| The bottlenecks in Rust builds arent't type checking/borrow
| checking. It's things like macro expansion, module
| resolution and compilation, codegen from LLVM IR, and
| linking. Some of these things have workarounds like
| sccache, using mold instead of the system linker, etc but
| others are problems that not only remain unsolved but are
| actively getting worse in the ecosystem.
| dralley wrote:
| >hours waiting for Rust code to link with mold because I
| added an eprintln somewhere
|
| How is that Rust's fault?
| saghm wrote:
| I think the "with mold" comment is expressing that even
| with the state-of-the-art fastest linker that's generally
| available for use with Rust, it still is slow. If you used
| the default linker, it would be even slower.
| throwaway2037 wrote:
| I'm not here to advocate for Lombok. However, you are first
| that I saw here to complain about much slower compile times.
| Can you teach me more? On a multi-core 5GHz desktop PC with
| 64GB RAM, who is really thinking about Java compile times these
| days? And I have worked on 1M+ line projects. Sure, the first
| compile is slow, but after, everything is incremental.
| imtringued wrote:
| Nobody complains about the Java compiler. Everyone complains
| about the class loading performance.
| javajosh wrote:
| It's not the compiler, in this case. Lombok generates code,
| and for reasons (which I have since forgotten) it _always_
| generates code and invalidates incremental builds in gradle,
| for example, at least when used in combination with ORM
| annotations. This happened at a previous contract on a
| company computer so I don 't have the links, but IIRC the
| gradle docs mention the interaction(s).
|
| I wanted to delombok to save 30s off of each build, but the
| rest of the team(s) refused to let it go. I thought, and
| still think, that was a short-sighted mistake.
| naasking wrote:
| > I've come to believe that the most important feature of any
| development environment is to minimize the built-test-debug
| cycle.
|
| I think the next innovation in statically typed languages
| should be to somehow break the build-test-debug cycle. We
| basically have the same interface to programming as dynamically
| typed languages, despite having considerably more information
| available because of the types. This should be exploitable
| somehow to give typed languages more advantages, and change the
| very nature of the loop.
| xiphias2 wrote:
| ,,I would have traded performance and expressivity away for
| simplicity''
|
| ,,A lot of people in the Rust community think "zero cost
| abstraction" is a core promise of the language. I would never
| have pitched this and still, personally, don't think it's good''
|
| If the language makes compromises in performance, it's not a real
| C++ competitor anymore.
|
| Some things are not about what people ,,like'', but that we need
| a language that is safe and can compete with C/C++ in performance
| for systems level programming, as most security problems in the
| world come from C/C++ memory management.
|
| If it's significantly slower than C++, Mozilla couldn't have
| picked it up to replace C++ code base, as there was a huge
| competition in performance between browsers.
| avgcorrection wrote:
| I would have had much less interest in Rust as a peanut gallery
| participant if it didn't have that goal of competing with C++.
| Not because I am a C++ or C programmer myself, but because it
| has been interesting to see if alternatives to such well-
| established languages can in fact be successful.
|
| Think about it: we have dozens upon dozens of Hoare-esque
| "sacrifice some performance for ergonomics" languages out
| there. But what are you gonna do when you need performance that
| the language is too inflexible for? Hmm... perhaps write some
| of your app in C...?
|
| If it weren't for Rust and similar languages, we would never
| have a hope of moving on to more modern languages for those
| "can't do this in my $mainlang due to performance" problems.
| How would yet another language that (according to Hoare):
|
| > and I would have traded lots and lots of small constant
| performancee costs for simpler or more robust versions of many
| abstractions.
|
| have helped with that? It wouldn't! You would have still been
| stuck with having to bind to C, C++, assembly, or whatever else
| sufficiently "zero-cost abstraction" language.
| bo1024 wrote:
| Yeah, I think most of this is implied in the article.
| bluGill wrote:
| Rust does make compromises of performance. That is not always
| bad so long as the compromises are minor. Fortunately Rust can
| do most of the checking at compile time, but it if you read
| from an empty vector Rust doesn't have undefined behavior and
| that means there is a runtime check of some sort in at least
| some cases.
|
| The trick is to find the right place to compromise so the cost
| is minimal overall even if it isn't zero.
| tialaramex wrote:
| The thing is, although the check looks like work, a _correct_
| C++ solution almost always needs to do the same work. There
| are real world cases where it doesn 't, but lots more where
| in C++ we need to explicitly do the work or our program
| malfunctions, sometimes in subtle ways - while in Rust we get
| this right by default.
| bluGill wrote:
| There are cases where you as the programmer have enough to
| know that there is data in the container even though
| anything less than whole program analysis cannot prove it.
| Rust will do unneeded check in those cases.
|
| I do agree though, the cost of the check is so small that
| it doesn't matter in most cases.
| Georgelemental wrote:
| Rust also gives you choices. If, for whatever reason, you
| _need_ to get rid of that runtime check, you can always use
| `unsafe`. Safety is the happy path, but you aren 't locked
| in.
| xiphias2 wrote:
| Dropping to unsafe rust is quite easy as long as you
| understand what you're doing (and reading pointer is
| generally unsafe).
|
| In the case of automatically upgrading from int to bignum, or
| green threads, going lower level would be much harder.
|
| I just wish Rust stayed focused on being the best systems
| level programming language (for example finishing the SIMD
| package, getting into stable Rust, getting language level GPU
| integration similar to CUDA / OpenCL).
|
| PyToch, and now the newer GGML for example is still written
| in C++ for example
|
| I still like the changes being made in the language, just not
| the focus.
| ch33zer wrote:
| What a wonderfully self aware and honest piece of writing.
| FrustratedMonky wrote:
| Great read from a creator of a language. There are so many new
| languages these days, it is always enlightening to hear the
| author discuss design tradeoff's. That being said, man, its easy
| to forget how difficult and complicated creating a good language
| can be.
|
| Makes me wonder if things like Linux, or C++, were historical
| anomalies, the stars aligned. How many good projects fail because
| of 'loosing arguments' that should have been won, or the
| community didn't form, etc... a million things..
| weinzierl wrote:
| One thing I wished was on this list, but wasn't, is syntax. I
| love many syntax decisions Rust made, but I wish Rust hasn't
| borrowed so much syntax from C/C++. The syntax of these languages
| was designed under (for todays standards) weird keyboard and
| encoding constraints and many choices are just odd.
|
| To give you a few examples:
|
| - = instead of == for equality would have been the natural choice
|
| - := for assignment is similar enough to what is used in math for
| definition, so that languages like Pascal use it
|
| - <> for inequality is something SQL got right
|
| Smaller things that bug me are the ubiquity of the double colon
| (::) and the weird mixture of snake case and camel case
| conventions.
|
| And not to leave the wrong impression, I think Rust got many
| things very right. My personal highlights are:
|
| - -> for the return value
|
| - concise keywords like `fn`
|
| - `where` for constraints
|
| In general more Algol/Pascal and Haskell - less BCPL and C/C++.
| pitaj wrote:
| Assignment is more common than equality. Typing two identical
| sequential characters "==" is much easier than typing ":="
| especially since on most keywords colon requires shift and
| equal does not. And having a two-character equality operator
| helps align things with "!=", ">=", "<=".
| weinzierl wrote:
| Equality is so much more common than the other relational
| operators that it justifies the single letter.
| jstx1 wrote:
| Syntax is mentioned in the article - it's under "Complex
| Grammar".
| epage wrote:
| > - := for assignment is similar enough to what is used in math
| for definition, so that languages like Pascal use it
|
| I think its cppfront that is taking the approach of `:=` being
| a declaration with the type being inferred (ie shorthand for `:
| Type =`). Reading up on that has made me the most ok with
| applying this to functions (which I see coming up more these
| days) but I think i still prefer functions having a more
| distinct look as I process them differently when reading. Now,
| cppfront's approach to types I think is bonkers, making
| critical details hard to find except maybe through convention.
|
| https://github.com/hsutter/cppfront
|
| > <> for inequality is something SQL got right
|
| Maybe I'm not recognizing the biases of my own learning
| background but this never reads right to me vs "not equal" /
| `!=`.
|
| > - concise keywords like `fn`
|
| In other discussions, it sounded like Graydon had an upper
| limit of 4 characters for keywords
|
| https://www.reddit.com/r/rust/comments/13oemrg/question_abou...
|
| For me, I had a "whoosh" moment for `fn` and always thought it
| a weird abbreviation, completely overlooking "fn" keys on
| laptops.
| weinzierl wrote:
| My very first programming language had fn, so maybe my love
| for it is just a form of nostalgia.
| junon wrote:
| > I was weirdly focused on [the Actor] model that in practice has
| many issues
|
| I maintain the actor model is probably the most theoretically
| perfect concurrency and distributed computing model. The holy
| grail. We just don't have the right hardware for it and it's
| extremely limited by addressability issues with current
| technology.
|
| So I don't really find this surprising, nor disagreeable. It's
| just not a model that Works Well at present.
| derefr wrote:
| I'm not sure I agree. Runtimes like Erlang's work great for
| many things.
|
| You just have to be aware that actors aren't going to magically
| get you more CPU cores -- i.e. you can have as many IO-bound
| actors as you want; but a CPU-bound actor (done correctly, such
| that "gets out of the way" of actor scheduling) is just a
| regular CPU-bound preemptive OS thread; and you can only
| _realistically_ have as many of those as you have CPU cores in
| your machine, before you start experiencing highly degraded
| performance.
|
| Most systems don't _need_ more than 100 (different) CPU-
| saturating things to happen at a time. If you do, actors won 't
| save you... but nothing else will, either. You'll need to scale
| horizontally. (At which point, the actor _model_ becomes very
| useful from another perspective -- that of transparent
| distribution of messages between nodes.)
|
| But tbh, there's a reason that, even on the systems Erlang was
| originally designed for and is "idiomatic" for -- those being
| telecom packet switches -- the Erlang software only performed
| the role of the _control plane_. There was also a _data plane_
| in each of those boxes -- some kind of FPGA or ASIC -- designed
| specifically for the job of applying a list of active routing
| rules at each input port, such that packets on that port would
| be unwrapped, maybe filtered, route-matched to an output port,
| maybe buffered to combine with other packets, then re-wrapped
| and emitted at said output port. The job of the Erlang code was
| to listen for _signals_ bubbled up from the data plane, in
| order to build complex state-machines, do accounting, etc.,
| resulting in _change commands_ being pushed back down _into_
| the data plane ruleset.
|
| Actors are great for keeping a bit of local state in order to
| make decisions, and coordinating with other actors and _their_
| local state to make more complex decisions.
|
| Actors implemented naively -- where all actors are uniformly
| green threads -- are not so great for doing the same thing over
| and over, at scale. Telling N actors to do the same thing is no
| substitute for a DSP, nor for a tensor core.
|
| But this isn't an indictment of the actor model, nor of current
| hardware, but rather of the current state-of-the-art in actor-
| model languages. You could totally create an actor-model
| programming language where actor-pools that are doing SIMD are
| transparently "promoted" during compilation into GPU shaders /
| FPGA gate-networks / etc. Nobody's done it, but there's nothing
| stopping anyone. (Anyone interested in trying this could
| probably do a proof-of-concept on top of Elixir's Nx library.)
| erik_seaberg wrote:
| I'm very glad expressivity won out. I would like our profession
| to stop accepting tools that waste effort.
|
| I've always seen safety and lifetimes and borrowing as the main
| value prop, so I was surprised to see he was sort of aiming at an
| ML without GC, rather than a C++ that doesn't blow up.
| dan-robertson wrote:
| Early rust did have gc so it was more of an ML with optional
| gc, and maybe a dot operator. (And no closures apparently)
| Aardwolf wrote:
| > Tail calls. I actually wanted them! I think they're great. And
| I got argued into not having them because the project in general
| got argued into the position of "compete to win with C++ on
| performance" and so I wound up writing a sad post rejecting them
|
| I don't understand the subtleties here: Is tail calls an
| optimization the compiler can do irrespective of the language? Or
| is there something that prevents this and _requires_ the compiler
| to use stack here? Is there any visible effect to the programmer
| from supporting tail call or not, other than performance and
| stack depth?
|
| How does not supporting them compete with C++ performance?
| [deleted]
| layer8 wrote:
| It's about stack size. Programs that rely on tail calls (in
| particular recursive ones) may cause the stack to overflow when
| the language implementation doesn't actually support tail
| calls, for example when using tail calls to recursively process
| a list that is larger than (some constant fraction of) the
| stack. With an infinite stack, it would just be an
| optimization, but in practice the stack is finite (and
| significantly smaller than the heap), and thus doesn't lend
| itself to such a programming style when the implementation
| doesn't support tail call optimization.
| pmontra wrote:
| That's why we write programs that rely on tail calls only for
| languages that have TCO in their specification. Where TCO is
| idiomatic (e.g. Elixir) implementations not supporting it are
| impossible because they will be able to run only the simplest
| programs.
|
| The problem is adding TCO to a language that was born without
| it. It won't break existing programs but you'll be able to
| run a program using TCO only with a specific compiler or
| interpreter from a given version. It could catch up if the
| main implementation of the language does it, but often we
| care about compatibility with the alternative
| implementations. Think about Ruby and Python, MRI and CPython
| and all the other implementations of the language. If I'm not
| wrong, MRI added TCO under a runtime configuration variable
| but JRuby doesn't support it.
| tinco wrote:
| I thought on 64 bit systems the stack could basically be
| infinite for all practical purposes. Is this only a problem
| on more limited (i.e. <64bit) systems or am I misremembering
| when I last learned about stacks 10+ years ago?
| aardvark179 wrote:
| It is always a problem, in the same way that holding on to
| huge objects is always a problem. Let's think about this in
| terms of a memory map, so ignoring limits of physical
| memory. On 64bit systems we actually have 48bits of usable
| address space, so that's 256TB. Now, the OS is going to
| need some workspace, but I think we can ignore that for
| this argument. So if you're only going to run 4 threads
| then each one could have a huge stack of 32TB, but if we
| wanted to run a huge server with a thread per connection
| then maybe we can only have 256MB per thread.
|
| Now, 256MB is pretty huge, but we probably want way more
| memory kept for heap storage and similar things (because
| managing lifetimes purely on the stack will be hard and
| might require a lot of copying of data), so it will be less
| than that. Now, you may ask, "Why not start with small
| stacks and make them bigger as needed?" It's a good
| question, but since our stacks are contiguous areas of
| memory and we don't know what's in them, we still have to
| space them out in our memory map allowing for as much
| growth as is needed.
|
| We might solve some of these problems by introducing
| segmented stacks, but this is one of those problems that
| crosses so many language, runtime, and OS boundaries that
| it's been hard to do in the general case, and it feels like
| gently nudging people towards writing code that can be
| tail-optimised is easier, just as it's easier to push
| people to use async than it is to provide systems and APIs
| that would allow for blocking code and a huge number of
| threads.
| tinco wrote:
| Thanks, I didn't realise the usable address space was so
| "small", those numbers are well within reasonable usage
| so that's definitely too limiting to just say it's
| infinite and not worry about it.
| layer8 wrote:
| Apart from address space, there's also simply the
| question of wasted resources, because in tail-call-
| optimizable programs, that stack space is by definition
| not strictly needed (otherwise it couldn't be optimized
| away), so you might unnecessarily be wasting many GBs of
| memory. Lastly, TCO can improve cache locality and thus
| potentially speed up the program.
| Merad wrote:
| Yes and no. In theory it would be totally fine to
| "allocate" a stack that was exobytes in size (or whatever
| extremely large number). Thanks to virtual memory no one
| really cares... if that memory isn't actually being used.
| But as your stack grows larger and larger the OS has to
| find pages of physical memory to assign to that allocation,
| and you're simply going to run out of memory. A high end
| home PC or average server is only going to have 64-128 GB
| of RAM, a very high end server might have a few TB. Of
| course when you start to run out of physical memory the OS
| will start paging (dumping data from RAM to disk to free up
| memory), but that will kill the performance of your app
| even with high end SSD's. If you keep chugging along with
| your stack growth you'll probably hit OS limits on page
| file size next, if not you'll eventually run out of disk
| space.
| Aardwolf wrote:
| What I actually meant to ask is: given what you said, is
| supporting it then even a _language_ feature, and not a
| _compiler_ optimization instead?
|
| If rust says they don't support it, does it mean they don't
| even allow the compiler to do it?
|
| Obviously the syntax of the language itself already supports
| calling the function itself, and whether tail call
| optimization is supported or not doesn't affect the visible
| result afaik (except in case of stack overflows /
| performance)
| bluGill wrote:
| Its not that the compiler cannot do it, it is that you
| cannot be sure the compiler will.
|
| Some constructs cannot be optimized for tail recursion.
| Languages with tail calls have guidelines of how to must
| write your code to ensure the tail call happens - often a
| seemingly small code change is the difference between tail
| call optimization happening or blowing up the stack.
|
| Second, in at least some cases where the optimizer can
| apply tail calls you need to be guaranteed it will happen.
| Nothing stops a C++ optimizer from applying tail call
| optimization (I don't know if any do, but it is allowed in
| some cases), but the language doesn't require it, so even
| if your optimizer supports it you can never know that it
| happens - and more importantly you cannot be sure that
| after changing the code or upgrading your compiler you will
| still get it. Thus even if your optimizer supports tail
| code optimization you dare not do deep recursion.
|
| If your language doesn't have support for tail calls you
| cannot do deep recursion with confidence. If your language
| does, then you can do deep recursion so long as you follow
| the rules of the language. (whatever those are - I'm not up
| on the latest research here, so I don't know the state of
| modern tail recursive languages are.)
| layer8 wrote:
| It's a language feature in the sense that it determines
| which programs are viable in the language. This is similar
| to garbage collection. In principle, you never have to
| explicitly free memory. Given infinite memory, garbage
| collection would only be an optimization. But since memory
| is finite, it becomes a language feature. Without garbage
| collection, programs that don't free their unused memory
| will typically run out of memory sooner or later, similarly
| to how a tail-calling program will run out of stack space
| if the language doesn't support it.
| Tyr42 wrote:
| I suppose a difference of speed turns into a difference of
| ability at some point. If you can't rely on tail calls, you
| will have to manually trampoline the calls, just in case
| the optimization didn't kick in and your program dies.
|
| Or like xmtp or mail or whatever, you generally write your
| client assuming you are going to talk to any spec
| compliment server, which can stop you from using some
| extensions if you aren't sure they will be supported.
|
| Possible is not the same as guaranteed.
| JonChesterfield wrote:
| Tail call optimisation means no work can be done after the call
| and before the return. You can't deallocate stack, call
| destructors, convert types, rethrow exceptions.
|
| Therefore _yes_ the compiler can always do it, but it may
| involve patching the called function to do some work. Splicing
| code into it and/or changing the calling convention. That's
| difficult to do for unknown caller/caller pairs, e.g. function
| pointers or separate compilation to machine code without enough
| metadata to patch it later. It runs a bit close to
| "sufficiently smart" compiler which usually means doable in
| theory but unlikely in practice.
|
| If you pick global designs to make them easier - probably most
| notably having the callee clean up the stack frame from the
| caller - and that makes other things slower, then you've given
| the competition an edge.
|
| Clang now has tail call annotations for C++, which works mostly
| because the compiler can reject them in the cases where it
| hasn't implemented the lowering. Rust can probably have it in
| the same approximate circumstances as C++.
| phi-go wrote:
| Interesting that tail calls are mentioned and implied to
| never come to Rust. I agree with your assessment and actually
| there is a RFC[1] in the works to support them. (Note that
| while I'm the author, this RFC is definitely a community
| effort.)
|
| [1] https://github.com/rust-lang/rfcs/pull/3407
| mhd wrote:
| Ah, Sather gets mentioned. That's a name I haven't heard in a
| long time. I remember looking at it during the latter half of the
| 90s when I was searching for the perfect OO language...
|
| https://www1.icsi.berkeley.edu/~sather/
| TowerTall wrote:
| The article starts with "In a recent podcast about Rust
| leadership, the BDFL question came up again"
|
| What it BDFL?
| Fiahil wrote:
| Benevolent dictator for life.
|
| "Benevolent dictator for life (BDFL) is a title given to a
| small number of open-source software development leaders,
| typically project founders who retain the final say in disputes
| or arguments within the community. The phrase originated in
| 1995 with reference to Guido van Rossum, creator of the Python
| programming language."
| dochtman wrote:
| Of course Guido ended up not completing the "For Life" part
| -- neither his life nor the life of the language. (I think
| PEP 8016 is the one that started the steering council to take
| the place of the BDFL? https://peps.python.org/pep-8016/)
| TowerTall wrote:
| Interesting. Isn't a BDFL needed in Open Source projects (not
| talking specific about Rust, but Open Source in general)? I
| work in a commercial software company. We have a CTO and he
| retain the final say in disputes or arguments. He also steers
| the global technical direction we take the software and has
| the final say, but he can get fired. Is that the main
| difference between a CTO and a BDFL?
|
| What alternatives are there?
| sanxiyn wrote:
| The usual alternative is a committee, see for example
| Apache Software Foundation's Project Management Committee
| Guide: https://www.apache.org/dev/pmc.html
| pm215 wrote:
| I suspect an informal grouping (i.e. more long-
| standing/senior contributors with default control over
| some parts of the codebase and operating by informal
| consensus between themselves) is probably at least as
| common (maybe more so) than an officially named committee
| with rules of procedure.
| zozbot234 wrote:
| The main difference between a CTO and a BDFL is that if you
| disagree with a BDFL, you can just fork the project under a
| new name. This right to fork is key to making FLOSS
| development work. If you disagree with a CTO, there isn't
| much that you can do besides buying up the whole company
| (see Elon Musk and TWTR as an example).
| emanuele232 wrote:
| The most inefficient decision-making structure known,
| democracy and voting?
| compressedgas wrote:
| https://en.wikipedia.org/wiki/Benevolent_dictator_for_life
| kjandersen wrote:
| Benevolent Dictator for Life, a governance model quite
| prominent in PL and OSS at large.
| [deleted]
| lhrvn wrote:
| A euphemism for a software project dictator where the moral
| superiority is baked into the title. The dictator does not have
| to be benevolent in practice.
| vintagedave wrote:
| > The other option, which I wanted, was for these to be compiler
| builtins open-coded at the sites of use, rather than library-
| provided. No "user code" at all (not even stdlib). This is how
| they were originally in Rust: vec and str operations were all
| emitted by special-case code in the compiler.
|
| One of the things I like about Delphi is that it has some
| powerful types as compiler intrinsics. Sets, strings (the
| compiler-generated code does call into RTL methods for things
| like finding substrings, but the string type itself), and so
| forth are all compiler-generated.
|
| I would like to see more, in fact: I think a map type would be a
| great inbuilt addition. (What I'd really like is compiler stubs
| so you could link in your own implementation. Whatever is linked
| in, it's then heavily optimised by the linker to be inlined etc
| as appropriate.)
| tormeh wrote:
| I think Rust is somewhat cumbersome as a language, but I also
| think the right tradeoffs were made. There are far fewer and far
| worse competitors in the high-performance space than in the
| general purpose space.
| mcguire wrote:
| " _Exterior iteration. Iteration used to be by stack / non-
| escaping coroutines, which we also called "interior" iteration,
| as opposed to "exterior" iteration by pointer-like things that
| live in variables you advance. Such coroutines are now finally
| supported by LLVM (they weren't at the time) and are actually a
| fairly old and reliable mechanism for a linking-friendly, not-
| having-to-inline-tons-of-library-code abstraction for iteration.
| They're in, like, BLISS and Modula-2 and such. Really normal
| thing to have, early Rust had them, and they got ripped out for a
| bunch of reasons that, again, mostly just form "an argument I
| lost" rather than anything I disagree with today. I wish Rust
| still had them. Maybe someday it will!_"
|
| I remember that one. The change was shortly after I started
| fooling with Rust and was _major._ Major as in it broke all the
| code that I 'd written to that point.
|
| " _Async /await. I wanted a standard green-thread runtime with
| growable stacks -- essentially just "coroutines that escape, when
| you need them too"._"
|
| I remember that one, too; it was one of the things that drew me
| to the language---I was imagining something more like Pony
| (https://www.ponylang.io/).
|
| " _The Rust I Wanted probably had no future, or at least not one
| anywhere near as good as The Rust We Got._ "
|
| Almost certainly true. But The Rust We Got is A Better C++, which
| was never appealing to me because I never liked C++ anyway.
| ZephyrBlu wrote:
| This is an interesting read, because I read it as "I would have
| done all these things which would have kept the language more
| pure to my vision but less accessible".
|
| I also found this bit interesting: "It's easier to work with than
| C++, but that's fairly faint praise".
|
| I see this kind of thing in my own personal projects all the
| time. I'm thinking "oh it would be really cool if I built X" when
| in reality most of the time users just want really simple stuff.
|
| Being easier to work in than C++ might be faint praise, but it's
| probably the biggest draw of Rust for me. I don't want to touch
| C++ with a 10ft pole, but I love using Rust.
| truculent wrote:
| I'm not sure it would be less accessible. I think the trade-
| offs would lean towards simplicity, and perhaps less
| familiarity. But with the right mental models, I think this
| could enhance accessibility (less "magic", or strange edge-
| cases). For example, see the discussion on not minding if users
| have to write something in a more verbose way if it preserves
| the language's principles. This type of trade-off is something
| that lowers the barrier to entry for beginners, who don't mind
| writing things out the long way.
| ZephyrBlu wrote:
| I'm using accessible in a very general sense. The sense of
| "how niche would this be". My impression is that Rust would
| have been a very niche language if Graydon was the BDFL.
| masklinn wrote:
| Note that the quote is specifically about parsing Rust syntax,
| it's not about the language in general (which is a _lot_ easier
| and safer to work in than C++).
| ZephyrBlu wrote:
| True, but I feel like the sentiment generalizes very well.
| I'm sure that C++ is perceived to have a similarly low bar in
| many areas.
| neonsunset wrote:
| I can't imagine Rust being even remotely viable without having
| generics or using LLVM to target a wide variety of platforms with
| sufficiently good codegen quality.
| krupan wrote:
| The key challenge of rust for me:
|
| "Complex grammar. I've become somewhat infamous about wanting to
| keep the language LL(1) but the fact is that today one can't
| parse Rust very easily, much less pretty-print (thus auto-format)
| it, and this is an actual (and fairly frequent) source of
| problems. It's easier to work with than C++, but that's fairly
| faint praise. I lost almost every argument about this, from the
| angle brackets for type parameters to the pattern-binding
| ambiguity to the semicolon and brace rules to ... ugh I don't
| even want to get into it. The grammar is not what I wanted.
| Sorry."
| kzrdude wrote:
| Is there a practical issue with this, or is this more
| philosophical?
| cryptonector wrote:
| Is there anything easier to parse than Lisp? Plus you get
| homoiconicity and `quote` is free. But... it's ugly.
| Lk7Of3vfJS2n wrote:
| Has Rust gone beyond the point of no return? Have less elegant
| features possibly been embedded in the language that have put
| Rust on the track to never become the hypothetically perfect or
| unblemished language?
| steveklabnik wrote:
| The answer to your second question is "yes" for every single
| language that's ever been made or will be made.
| Decabytes wrote:
| I know we haven't gotten to that point yet, but I'm curious to
| see if in my lifetime we get a successor to Rust that fixes a lot
| of issues people have with the language now.
| anonyfox wrote:
| I love Rust and built some production code with it in the past.
| But nowadays I want something more simple so that not-so-senior
| developers can pick it up quickly, and I want flawless tooling,
| and willing to sacrifice a bit of performance. So basically I
| often end up with Go. Go is exceptionally great in tooling,
| ecosystem, any objective metrics like build times or
| crosscompilation... but I still don't like the language itself
| personally.
|
| If there would be something just like Go, but with a bit more
| powerful typesystem like Rust has (Option<T> instead of `err !=
| nil`, and so on), and a simplified ML-like language instead of an
| imperative one... that would be my dream.
| gardaani wrote:
| Rust borrow checker and lifetimes are sometimes so frustrating.
| I hope that the Jakt language will evolve to the language I'd
| like to see: Swift without ties to Apple and Objective-C.
| bscphil wrote:
| Apple has been pushing Swift on Linux enough that I've been
| checking out its progress every so often. It will probably
| never get entirely free of ObjC but it's a nice enough
| language that I'd really like to be able to use it for cross-
| platform desktop applications. Having a pretty robust
| standard library and a stable ABI (currently just on macOS)
| are big plusses, in my book. Having a built in REPL is icing
| (delicious, delicious icing).
| amelius wrote:
| OCaml?
| anonyfox wrote:
| really tried to get into it for a while, but the tooling
| situation feels like the polar opposite of Go, where
| everything needed in daily work is right there in a
| standardized way and just works out of the box.
|
| I kinda like the language (its what I want basically) but the
| operational aspects are what I actually need and want first
| and foremost.
| scythe wrote:
| >like Rust has (Option<T> instead of `err != nil`
|
| Didn't they implement generics already?
|
| https://go.dev/blog/why-generics
|
| It seems like their implementation has enough power to do this
| in most cases, although "Option" is not built in.
| kaba0 wrote:
| Scala, Haskell, Java, Kotlin.
| [deleted]
| netbioserror wrote:
| Nim gets real close. It doesn't have great UI tooling, but its
| command line tools are quite good.
| xhevahir wrote:
| Nim is quite imperative, too. And its BDFL(Araq) takes a dim
| view of functional programming: https://forum.nim-
| lang.org/t/8927
| rdevsrex wrote:
| Yeah I've been looking into Nim and I want to build something
| substantial in it and see how it performs and feels to write.
| YorickPeterse wrote:
| Perhaps you'd be interested in Inko (https://inko-lang.org/).
| It's obviously not there yet in terms of tooling and what not,
| but it might scratch an itch for those looking for something a
| bit like Rust, but easier to use.
|
| Disclaimer: I'm the author of said language :)
| justinpombrio wrote:
| Lots of languages are aiming for "like Rust, but easier to
| use" -- I think I could name half a dozen. It's a laudable
| goal!
|
| I'm curious how references in your language work. I see the
| very small example, but it doesn't explain much.
|
| Some questions in that regard: Is `&T` a type? Can you store
| it in a structure, or return it? Can you have a reference to
| a reference? If you can have a function `f(&T, &T) -> &T`,
| how do you distinguish whether the reference it returns lives
| as long as the first or second argument? If references can't
| be stored in structs, how do you do non-owned iterators, or
| string slices?
| jeltz wrote:
| Yeah, that is exactly what I want for when I build web
| applications. Something with an ecosystem like Rust's, with
| more advanced type system than go but which sacrifices some
| performance for ease of development.
| californical wrote:
| I feel like Crystal is close, they just need more community
| to build out their ecosystem. Otherwise it seems like a
| perfect language (fun, simple to understand, compiled + fast,
| has types)
| anonyfox wrote:
| Already played with it, what has killed me are the
| outragously long compile times for nontrivial code and no
| "easy" way to crosscompile binaries, besides from some
| docker hack. Also its like a modern Ruby, which is great in
| general, but I really want ML-like functional style
| programming instead of OOP
| weatherlight wrote:
| I wish someone would go back and give love to Standard ML. it
| such a great language, small, and easy to learn, extremely
| fast, compiles fast, and expressive to boot.
|
| It's tooling isn't great though. I think its syntax is a lot
| nicer than OCaml's for example.
|
| There is this wonderful language that is up and coming called
| Roc that look promising.
|
| https://www.youtube.com/watch?v=6qzWm_eoUXM
|
| language examples here -> https://www.roc-lang.org/tutorial
| jjtheblunt wrote:
| Have you dismissed F# ?
| hajile wrote:
| The rest of the world has. If you're going to use .net,
| there's almost zero chance that you'll get permission to use
| F#.
|
| For whatever reason, C# devs seem incredibly resistant to
| even just looking at F#.
| pjmlp wrote:
| Do you want reasons? Here are a couple from someone that
| has learned ML with Caml Light, and most of the latter ML
| variants.
|
| VS Tooling lacking versus C#/VB, no support for code
| generators, no support for Roslyn, no support for GUI
| frameworks, no support for EF tooling, many .NET vendors
| don't support projects if using F#, community likes to
| create their own wrappers instead of embracing standard
| .NET projects, ....
| FrustratedMonky wrote:
| I would like to second F#. It seems to have a lot of what
| people want, so why isn't it more popular?
| anonyfox wrote:
| Can I natively build self-contained binaries? One of Go's
| biggest advantages is the delivery chain from code to
| server (build for target arch, copy to target, ./run)?
| naraga wrote:
| I believe you can https://learn.microsoft.com/en-
| us/dotnet/core/deploying/sing...
| throwaway2037 wrote:
| I can vouch for it! I returned to C# for a tiny desktop
| GUI recently. I was blown away by how simple is the
| tooling to create a single monolithic binary. It's great
| for enterprise distribution. "No more installer."
| jjtheblunt wrote:
| Yes
| ontologiae wrote:
| I had to deal with it, when I hadto parse Transac-SQL
| code, and it's not handy. Very difficult to have a single
| executable file containing everything, it needs a whole
| subtree. OCaml is easier with Opam and ocamlbuild tools.
| zidad wrote:
| Unfortunately a lot of people will dismiss C# and F# just
| because it has the Microsoft label. I do think they're
| missing out.
| anonyfox wrote:
| Nowadays I program in a mostly Microsoft world (VSCode,
| Github, npm, typescript, GPT, ...) and I think its quite
| good, so no bias here. But the "shipping a native binary
| straight to the server that just works" is absolutely
| crucial for me
| walljm wrote:
| .net will let you do that now. You can ship precompiled
| binaries with Native AOT.
| (https://learn.microsoft.com/en-
| us/dotnet/core/deploying/nati...)
| [deleted]
| vips7L wrote:
| And even before native AOT you could use publish single
| and have a single binary.
| mmargerum wrote:
| ASP.NET AOT support coming in .net 8
| zidad wrote:
| You could try C# (or Kotlin if you have a MS bias ;-)). Both
| really nice general purpose languages, quite performant despite
| having a GC and great available tooling.
| anonyfox wrote:
| I think I still would have to deal with a lot of OOP and
| imperative code there, C# feels like a kitchen sink of stuff
| - right?
| [deleted]
| zamalek wrote:
| C# is horribly OOP. I use it in my day job, and the
| frameworks wield OOP overcomplexity proudly. It has likely
| gone beyond Java as the posterchild for OOP: interfaces
| that are implemented once, classes that are instantiated
| once. You can't get away from it either, it is
| _practically_ part of the stdlib and everyone cargocults
| it.
|
| Rust is imperative too, though.
| hadrien01 wrote:
| The language doesn't push you towards interfaces
| implemented once, but many developers indeed persist
| doing it for no reason at all. With proper code review
| we're able to make that practice go away on the projects
| I'm working on.
| vips7L wrote:
| Isn't this just mostly an issue with mock testing in C#?
| Developers cargo cult single implementation interfaces
| because its so hard to mock concrete classes.
| rjbwork wrote:
| >Isn't this just mostly an issue with mock testing in C#
|
| Yes. You have two choices: Interface implemented once, or
| virtual on all your public members.
|
| I personally think Interface is the sane choice.
|
| Would be nice if the .NET devs let us mock POCO's
| though...
| hadrien01 wrote:
| You can mock (well, "fake") POCOs with packages like
| AutoFixture that use reflection to generate mostly fake
| data, depending on what you need. You don't need
| interfaces or virtual, but you do need public getters
| unfortunately.
| za3faran wrote:
| I've seen the same in golang, which ironically is
| supposed to be vehemently against OOP.
| diarrhea wrote:
| Lots (all?) major frameworks push for dependency
| injection though, where interfaces are a must, as far as
| I know (not for DI-the-principle, but DI-as-implemented).
| ASP.Net Core is a good example. It's not C# at the
| language level _forcing_ interface-driven development,
| but frameworks like ASP.Net are so tightly integrated
| that boundaries blur.
| zamalek wrote:
| Yup, that's what I meant by _" practically."_ Nobody
| forces you to use the Microsoft.x nuget packages, but
| good luck at finding packages that don't rely on
| Microsoft.DI, or Microsoft.Hosting, or what-have-you.
| hadrien01 wrote:
| You don't need interfaces for dependency injection, you
| can inject classes directly. I see interfaces more for
| separation of concerns (with domain-driven design pushed
| from all parts in the .NET world at the moment...), but
| that's not an obligation.
| chronofar wrote:
| You can avoid quite a bit of OOP with Kotlin if you like,
| and with Arrow make some really nice functional code. Imo
| it offers a great balance of imperative vs functional,
| providing powerful tools for whatever style suits the
| project best.
| avgcorrection wrote:
| > If there would be something just like Go, but with a bit more
| powerful typesystem like Rust has (Option<T> instead of `err !=
| nil`, and so on), and a simplified ML-like language instead of
| an imperative one... that would be my dream.
|
| Not to be snarky (for once) but
|
| - More powerful type system: that goes against the whole
| implementation culture behind Go and their wider philosophy
|
| - ML-inspired instead of imperative: goes _even more_ counter
| to the above
|
| I have never tried Go and I will probably never care to try it,
| but I have never seen a language which manages to both be (1)
| simple in the Go-sense and (2) look remotely anything like an
| ML language.
| pjmlp wrote:
| While it has a been a great piece of wrong to introduce affine
| type systems into mainstream, it hardly justifies outside domains
| where any kind of automatic memory allocation isn't either a
| blocker, or religious issue that won't be sorted out even by
| proving the contrary.
|
| I see the ongoing attempts to add linear types for low level
| coding, alongside automatic resource management more future
| proof.
| truculent wrote:
| It sounds like the Rust He Wanted has a lot of thematic
| similarities to Elm. Interestingly, Elm has a BDFL, and a
| development process that reflects that. And he's right - there
| are a lot of people who really don't like that!
|
| Overall, a really interesting article. Though I like today's
| Rust, I do think I would prefer the trade-offs made by the alt-
| Rust outlined here.
|
| Perhaps it's just my own personal preference, but I think there
| is a strong bias in users towards what they are already familiar
| with, and it's hard to break away from those without a BDFl or
| similar position of authority who can impose their vision.
| EdwardDiego wrote:
| My understanding is that for the Elm BDFL, the B was dropped.
| truculent wrote:
| The language creator has preferred to work on the development
| of the language largely in private, for the last couple of
| years. He's explained why he's chosen to do this and why he
| thinks it is beneficial. But, understandably, some people
| haven't take too well to the lack of updates.
|
| I think whether you view that as benevolent or not (I would,
| because I agree with lots of de facto development standards
| being detrimental to language quality, but I understand that
| that is my opinion and not hard fact) is in the eye of the
| beholder.
| EdwardDiego wrote:
| I was more thinking of a) the ring-fencing of native code
| and b) his conduct in discussions about such things. Guido
| was never that abrasive.
| mjburgess wrote:
| Over the evolution of Rust, I've been increasingly despairing
| about many of the things Graydon here dislikes. I assumed the
| present "syntactical insanity" was, somehow, intended; it seems,
| really, it wasn't.
|
| I find Rust basically unusable -- at the level of abstraction I
| want to write code, basic definitions break line limits.
|
| Rust seems to be a repetition of C++'s mistake: a language which
| conspires you to pretend it's another. There are now nearly as
| many Rusts as C++s.
|
| If I return to any domains where Rust would be relevant, I'd
| probably now opt for Zig or equivalent.
| laeri wrote:
| Can you give an example where Rust is unusable? I have been
| getting into Rust recently and it feels very natural after
| getting accustomed to dealing with `Result` and `Option`. And
| what do you mean about "a langauge which conspires you to
| pretend it's another"?
| kaba0 wrote:
| Rust is a low-level programming language meant to control
| everything about the code's execution. It will by almost
| definition, be complex.
|
| You seem to want contradictory things -- if you don't need that
| level of control just use any of the litany of high level
| languages with nice syntaxes. I don't think we can eat this
| cake anytime.
| marcosdumay wrote:
| Yeah... Rust was first intended to be a high-level language
| where one could create an entire browser engine without
| sacrificing a lot of performance.
|
| The fact that it is a law-level language best fit for system
| programing came much later. Late enough that there are people
| here and there still trying to use it for the original use-
| cases.
| mjburgess wrote:
| The major innovation in this space is to make the compiler an
| interpreter which can evaluate the language at compile time
| (and emits code for runtime where it cannot).
|
| This makes brining in the "full power of dynamic languages"
| almost trivial and extremely performant.
|
| Delete half of rust's symbols, get rid of its macro system,
| rewrite it until lifetimes are inferred /or/ allocators are
| explicitly chosen, etc. etc.
|
| I don't want to feel visually assaulted when writing the type
| signature of the sort of function common in a dynamic
| language. Here `mypy` is also treasonously guilty.
|
| Consider, eg., zigs "function which _returns_ a type at
| compile-time " is an example of where blindingly-obvious
| syntax retains its blindingly-obviousness because of compile-
| time eval... ie., we dont need a "second syntax" to program
| the compiler.
|
| This "two syntaxes, one for the runtime and one for the
| compiler" approach -- has swamped Rust as it aims for greater
| expressiveness. Not least, because it has a third syntax: one
| for unsafe.
| kaba0 wrote:
| You quickly gets into the undecidable category once you to
| down that road, which might be fine if you either don't
| care about safety, or have runtime checks for that, but
| none of those were an option for rust (or at least an
| option that would have made it remotely interesting).
| zozbot234 wrote:
| It's undecidable in the general case, but it becomes
| doable in non-Turing-complete languages that are still
| expressive enough to be useful for many practical cases.
| (These languages can express unrestricted recursion as an
| I/O-like effect that's only available outside the
| language proper, as part of compiling to a binary.)
| kaba0 wrote:
| Lifetimes only work because they are very restrictive -
| the moment you can't decide that you end up with a non-
| safe language.
| mjburgess wrote:
| Soundness, Completeness, Saftey -- I care about these far
| less than
|
| Safe, Sound, Complete 99.999% of the time; a joy to use
| 100% of the time
| kaba0 wrote:
| Then you are not the target audience of a low-level
| language that supposed to run without a runtime, but
| still safely. Don't try to change the tool, when you
| could just choose a more fitting one.
| zozbot234 wrote:
| Rust has an unsafe subset for that 0.001% of tne time you
| need it.
| steveklabnik wrote:
| (Unsafe is a superset, not a subset, incidentally)
| awestroke wrote:
| > Delete half of rust's symbols, get rid of its macro
| system, rewrite it until lifetimes are inferred /or/
| allocators are explicitly chosen, etc. etc.
|
| Feel free to do this. I predict you will end up with a
| language that appeals to nobody but yourself
| mjburgess wrote:
| Well, I'm more-or-less describing Nim.
|
| I imagine it has more users than just me.
|
| My version would, of course, be Nim++
| avgcorrection wrote:
| > The major innovation in this space is to make the
| compiler an interpreter which can evaluate the language at
| compile time (and emits code for runtime where it cannot).
|
| Show us an extant language that does this, then.
|
| Partial Evaluation (automatic) was a very unsolved problem,
| last I looked.
| duped wrote:
| > This "two syntaxes, one for the runtime and one for the
| compiler" approach -- has swamped Rust as it aims for
| greater expressiveness. Not least, because it has a third
| syntax: one for unsafe.
|
| What are you referencing, concretely? For example, "unsafe"
| doesn't add or remove syntax.
| mjburgess wrote:
| Split the syntax into whether it 1) emits no code
| (compiler-facing); 2) emits weakly-related code (eg.,
| macros, etc.); 3) emits code close to what's written
| abstractly (eg., value operations); 4) emits code close
| to what the machine needs (eg., unsafe code) -- etc.
|
| And you'll find about "4 languages" all mixed together.
| duped wrote:
| I still have no idea what you're talking about. Can you
| give an example?
|
| Most "syntax" in AOT, statically typed languages does not
| directly generate machine code, but it does directly
| impact what machine code is generated. So there's not a
| clear distinction in practice.
|
| For example, a lot of the syntax is used to control
| method selection and verification - that's not a
| "different" syntax or language by most folks'
| definitions.
| mjburgess wrote:
| Compare this with lisp, where there's _one_ syntax --
| compiler operations are "compile-time" interpretations
| of the very same syntax.
|
| Or below, let's invent a language where there's (in my
| sense) "one syntax for everything",
|
| Eg., consider something like, const
| SimpleTrait = trait with: val name
| def GenericApiTrait(type T) = return new
| trait with: def response : self -> str
| const MyAPI = GenericApiTrait(SimpleTrait(new class))
| with: val name = "World" def
| response : self -> str = f"Hello $name!" #
| later on, in the app, def calc(x, y) : int,
| int -> int = return x + y #
| ie., the **same** syntax as that earlier which targets
| compile-time
|
| Here you can see that code with ordinary run-time
| semantics is used for the compile-time operations of
| generating a trait and specifying that a class implements
| that trait (ie., we call the trait-defining function on
| the class).
|
| Whereas in Rust, the syntax for returning values and
| "quantifying over types" is radically different.
|
| This is the historical approach (C++, C# .. almost all
| langs) -- but not one modern innovative langs follow.
|
| I think with as much desire for novelty rust has,
| cramming it all into its "compile-time syntax" has
| hobbled it.
|
| A simple uniform language for both c-time and r-time
| could be used
| duped wrote:
| Ah ok, I see what you're saying. It's an interesting
| idea.
|
| One of the reasons that something like unified syntax is
| not preferred is because there are some important
| distinctions between runtime and comptime, and a lisp-
| like syntax doesn't translate well because lisp doesn't
| have a meaningful distinction between the two.
|
| For example, it's desirable for some folks to have the
| "comptime" work (declaring types, imports, function
| signatures, etc) to have a purely declarative syntax. And
| languages like Rust use declarative syntax to express
| things about types, interfaces, and their constraints.
| Contrast that with the logic of the program which is more
| convenient for a programmer to express in the imperative
| style.
| pmontra wrote:
| > The priorities I had while working on the language are broadly
| not the revealed priorities of the community that's developed
| around the language in the years since
|
| Isn't that a matter of self selection? A language which developed
| around those priorities would have a community sharing those
| priorities now.
|
| The point is if that community would be as large as the current
| one, larger, smaller.
| kryptiskt wrote:
| It's not really mentioned in the post, but one thing Rust had
| since fairly early on was a use case, it was used to build a
| browser engine. As I remember it from the time, the feedback
| from Servo development tended to pull Rust in a more
| performance-oriented direction.
| layer8 wrote:
| The fact that the community that formed trended in that way can
| be taken as an indication that those directions attracted more
| people than the original vision. Note how often Graydon was
| basically outvoted on design decisions. It would have been
| conceivable for the project to attract mostly people that fully
| shared Graydon's vision, but that's apparently not what
| happened.
| w10-1 wrote:
| Graydon may lament the features that got away, but being a good
| loser may be the best way to encourage contributions and to
| respond to community needs. The historical forces on a language
| are both its constraints and its drivers. Languages that are nice
| conceptually are not responsive to history.
|
| Both Swift and Rust both veered away from their original
| champions. The champions helped by focusing the problem and
| providing a technical skeleton, but the need (the pain of
| C/C++/Objective-C) was both intense and complex, so the community
| was stronger than the BDFL model.
|
| Interestingly, Swift has seen Rust forge ahead on a number of
| fronts, but is quietly adopting the best of Rust, and soon
| interoperating with C/C++ will be frictionless. The ties to Apple
| are being loosened, with a more portable stdlib and a Foundation
| library that subsets the legacy Apple Foundation instead of
| dragging Apple API's into other platforms. If/since Apple is to
| rewrite its systems in Swift, Swift will likely evolve into the
| best language for migrating off C/C++.
|
| Compare Graydon, von Rossum, or Chris Lattner to Java's Mark
| Reinhold. Mark has been quietly at the helm of Java since 1997,
| navigating: the Oracle and open-source transitions, partners
| ranging from IBM to broad developer communities, continuous VM
| updates that kept Java relevant, and the quick pace of recent
| language/library upgrades: lambdas (method and field handles),
| vector processing and FFI, native...
| bazoom42 wrote:
| To be successful, it is not enough for a language to be good. It
| might not even be necessary. What matters is if there is s
| significant niche where the language is a better fit than any
| alternative.
|
| PHP show that a language only needs to get that one thing right.
|
| Rust have found its niche. Graydons vision seem to be a more
| elegant language which would compromize on the points which
| actully make Rust succesful.
| kaba0 wrote:
| Well put. Rust wouldn't be anywhere near this big/hyped if it
| would have been a new ML.
| sapiogram wrote:
| > What matters is if there is s significant niche where the
| language is a better fit than any alternative.
|
| This is a wonderful comment, and puts into words something I've
| been thinking for a long time. The best general-purpose
| languages always seem to start out with a strong niche, then
| grow from there.
| 0xDEF wrote:
| Rust moved in a direction where it is now a suitable alternative
| to C/C++ all the way down to operating system code and bare-metal
| embedded firmware.
|
| Graydon wanted something else even before Rust 1.0 was released.
| He wanted an OCaml-like language with modern Go/Erlang-inspired
| higher level concurrency abstractions.
| zozbot234 wrote:
| Technically, Rust had no future prior to the 2018 edition. The
| fact that Rust can add new features as the use cases for it
| evolve is a strength of the language, one that it had from the
| start even with Graydon as a BDFL.
| sph wrote:
| Very interesting insight from Graydon, in hindsight I too would
| have loved something more towards ML than C++. I never liked the
| kitchen sink approach that I see first C++, now Rust moving
| towards, but I respect what Rust has managed to solidify into.
| It's a good language.
|
| That said, I still hate async with a passion, it makes the
| language more complex and not very elegant (i.e. function
| coloring). And now that I know how it works behind the scene
| (thanks to Jon Gjengset [1]), it feels so complicated and hacky,
| a mediocre very high level concept that someone managed to
| implement as a zero-cost abstraction. Impressive, but still a bad
| idea.
|
| I'm sure the pro of having a BDFL instead of a committee is being
| able to follow a singular vision, instead of trying to appease
| members by adding the fad du jour which might stray a little too
| far from the original vision. Too many chefs in the kitchen and
| all.
|
| 1: https://www.youtube.com/watch?v=ThjvMReOXYM
| throwawaymaths wrote:
| > a zero-cost abstraction
|
| It's just an abstraction, it's not zero-(runtime)-cost. It
| _might_ be the "lowest possible cost", still nonzero.
| littlestymaar wrote:
| > (i.e. function coloring)
|
| I really wish people stopped using this concept, especially in
| the context of Rust because the `async fn`/"regular function"
| split is _strictly equivalent_ to the `try_something()`
| /`something()` split (the first one being failible and
| returning a `Result` in case of failure). `Result`s and
| `Option`s are coloring the stack in _exactly the same way_ a
| `Future` does (and `async` is pure syntactic sugar on top of
| future).
|
| So, someone may like exceptions and green threads more than
| `Result` and `async` (and this is a completely valid PoV, even
| though I personnaly like the explicitness better), but thinking
| `async` is somehow special is just a conceptual mistake.
|
| BTW, in the original blog post the "red" functions were
| actually functions _with a callback parameter_ , which is
| actually very different.
| chmod600 wrote:
| There's consistent use of Result and Option in std, community
| agreement to do things that way, and it's pretty easy to use
| them.
|
| Tons of stuff in std and many other crates don't use async. A
| lot of stuff is harder and more code to do async. A lot of it
| is much harder to read.
|
| That's the difference.
| littlestymaar wrote:
| > There's consistent use of Result and Option in std,
|
| Well, there are dozens[0] of incompatible error types in
| std, two of them being just called `Error`[1][2].
|
| Everytime you create a function that use more one of the
| different error types from std (for instance, an I/O error
| and a _TryFrom_ -related error) you have to create a custom
| Error enum to accommodate the different kinds of errors
| from std...
|
| > community agreement to do things that way
|
| And the community agreement is error-chain, failure,
| snafu, this_error, unless it's for a binary crate and then
| it's anyhow, eyre, color-eyre.
|
| The error ecosystem is much more fragmented than the async
| one (even though I think _this_error_ is here to last, so I
| don 't have to migrate once again...). In the async world,
| there's pretty much _tokio_ (but I think it would actually
| be better if it interoperated better with other runtimes so
| alternatives could emerge)
|
| > A lot of it is much harder to read.
|
| Ah yes, _fut.await_ is fundamentally harder to read than
| _res?_ , right?
|
| Overall, you vastly underestimate the friction that the
| `Result`-based error handling adds. I still think it's
| worth it, and it's been slowly getting better (did I say I
| loved _this_error_?), but it remained one of the biggest
| point of friction in Rust over the 8 years (time flies) I
| 've been using it. Async is comparatively less disrupting
| (mostly because it only appears in the call stacks where
| you're doing I/O, whereas errors are ubiquitous).
|
| [0]: https://doc.rust-
| lang.org/std/error/trait.Error.html?search=...
|
| [1]: https://doc.rust-lang.org/std/io/struct.Error.html
|
| [2]: https://doc.rust-lang.org/std/fmt/struct.Error.html
| pornel wrote:
| Function coloring article conflated two things: one was
| legitimate limitation of JS unable to wait for async result
| from sync call, and the other was just author's opinion how the
| syntax should look like.
|
| Rust's async doesn't have the limitation author described - you
| can spawn and block both (Tokio has some limits there, but
| that's Tokyo's choice, not language limitation), making "color"
| largely irrelevant.
|
| The second point was that both should have identical syntax,
| which Rust deliberately chose not to, because from Rusts
| perspective that would be too much implicit magic.
| sph wrote:
| I disagree. You can call an async function in Rust from a
| regular one, that's true, but the returned value still needs
| to be passed through an executor to be useful.
|
| Async without function coloring means being able to call
| `async fn add(a, b: usize) -> usize` from anywhere and having
| an usize back, whether you're calling from a regular or async
| function. If you need slightly different logic because of
| async, you got function coloring.
|
| In fact, a hypothetical _async without coloring_
| functionality would not even need the `async` keyword at all,
| as all functions would be effectively the same, so you could
| choose to call one asynchronously or not with no particular
| ceremony. Is this even possible to do without compiler magic?
| xmcqdpt2 wrote:
| You can do it using a runtime with scheduling and fibers.
|
| The issue is that you want to be able to save the entire
| thread stack cheaply so that you can switch to another task
| at an async yield point, and then go back to your previous
| stack frame. You want to do this without spawning system
| threads.
|
| Say you have an f -> g -> h call stack that blocks on IO.
| If all the functions are in fact async state machines (or
| some other kind of callbacks) your thread stack will look
| like this:
|
| Executor.loop() -> Executor.run(h) -> h.await()
|
| That is, your functions are really objects that are queued
| up and taken as needed by the executor. If h has a yield
| point, you can put it back on the queue, go back to the
| loop and pick up some other work. Later you can do the
| above again and await h to again up to the next yield
| point.
|
| Now consider the case where the middle function g is NOT
| async. In that case the call from f to g will be a normal
| function call that gets put on your call stack. In a
| language where this is allowed, you'll still have to wait
| or execute h and you'll get a call stack that looks like,
|
| Executor.loop() -> Executor.run(f) -> f.await() -> g() ->
| h.awaitUntilCompletion()
|
| (where our awaitUntilCompletion is minimally just calling
| h.await in a loop until h is finished). At this point, you
| are stuck: you can't yield from g() because it is a normal
| function with a normal stack frame, so your thread has to
| wait till g() finishes before it can be used for anything
| else. You are basically blocking a thread on h. At this
| point you might spawn a new thread to keep your thread pool
| count up (which is expensive and memory hungry) or just
| accept the forced synchronous blocking (which reduces
| throughput).
|
| If this happens over and over again, you either end up
| running out of memory or end up running code in a
| synchronous fashion but with worse performance (because of
| all the state machines). This is why functions are usually
| coloured, so that you don't get yourself in this accursed
| state by accident. AFAIK the alternative is to allocate all
| of your call stacks in the heap so that you can switch them
| in and out of kernel threads, which is what fibers are.
| This requires a complicated runtime with a scheduler such
| as in Loom,
|
| https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html
| ohgodplsno wrote:
| Not knowing about function coloring is a stupid dream and
| has terrible implications on the performance of your code,
| which is infinitely more important than you losing 2
| minutes having to figure out that you really want to
| `runBlocking { callThatBlocksForADamnLongTime() }`.
|
| The insight that function coloring gives you is terribly
| important. Roman Elizarov (of Jetbrains, author of Kotlin
| and lead on Kotlin Coroutines) agrees too:
| https://elizarov.medium.com/how-do-you-color-your-
| functions-...
|
| async functions without coloring means that the only
| warning you'll ever get that `calculate2Plus2()` actually
| ends up running a distributed BigQuery and writing the end
| result to disk, printing it to stdout() and parsing that
| result to give it back to you is... hopefully,
| documentation is up to date and you read it?
|
| Async function coloring is not a problem. Async function
| coloring is a solution to "software developers are awful
| and will do awful things without any warning". If
| `calculate2Plus2` did not exhibit the write to disk
| behaviour in v1.0.0, but bumping to v1.0.1 does, I'd really
| want a warning that it does at least, and ideally a
| compiler error. The proper solution to function coloring is
| to have a pleasant API to interop between both worlds so
| that, at worst it's just a dozen characters more to say
| "yep, I really want to block here".
| mike_hearn wrote:
| Async doesn't solve that problem. What you're asking for
| is some sort of expression in the type system of expected
| performance, but there's no tech that can do this.
| Consider that modern NVMe SSDs can do disk reads faster
| than many calculations over the content of what was just
| read. A function that changes its algorithm to have worse
| time complexity won't be marked as async but could break
| your app, adding a single disk read would require marking
| as async but probably won't break your app (especially if
| it's in a non-hot path), adding 10,000 more reads where
| previously you had one doesn't require changing your
| async annotation but might break your app, etc.
|
| So the whole sync/async distinction doesn't really make
| much sense outside the context of single threaded UI
| toolkits, where indeed you have to read the docs anyway
| because doing some very CPU intensive work on the UI
| thread will block it even if no "blocking" APIs are ever
| invoked (and what is or is not blocking is somewhat
| arbitrary anyway in many cases).
| hayley-patton wrote:
| There are type systems which can track complexity (e.g. h
| ttp://gallium.inria.fr/~fpottier/slides/fpottier-2011-01-
| jf...) but that's still different to performance;
| async/sync tells us things like "touches disk" or
| "touches network" but not "O(2^n) might take a really
| long time".
|
| Alternately, an effect system can communicate "touches
| disk", can make functions which are polymorphic to
| effects (e.g. map touches the disk if the mapping
| function touches the disk), and can distinguish different
| effects, unlike async/sync being anything-or-nothing. IMO
| async is more of an implementation detail (as "may
| suspend/yield" is not a very precise or interesting
| effect), whereas effects would communicate the properties
| that ohgodplsno wants to know.
| mike_hearn wrote:
| Yes, but are there any effects systems in use outside of
| maybe Haskell? Effects seem mostly to be stuck in the
| research lab and have been for a long time.
| hayley-patton wrote:
| I don't mean "effects" in the sense where you can have
| effect handlers which get delimited continuations and
| all; it'd just mark what could happen (like checked
| exceptions). But I can't think of any languages with that
| and not effect handlers; Koka, Eff, and Unison come to
| mind for effects, though their practical-ness may vary.
| zozbot234 wrote:
| Do you really need effect handlers as a separate feature
| once you have async/await? It seems like you could easily
| encode one with the other.
| hayley-patton wrote:
| An effect handler is allowed to invoke a continuation
| multiple times; I believe this is not possible with
| async/await.
| ohgodplsno wrote:
| Verse has effects, which by virtue of exposing it to the
| entire Fortnite community probably makes it a bigger
| language than Haskell in terms of user counts.
|
| https://dev.epicgames.com/documentation/en-
| us/uefn/specifier...
|
| converges/diverges, computes, varies, reads, writes,
| transacts, no_rollback.
| ohgodplsno wrote:
| Adding a disk read won't break my software, but it can
| certainly make my software non-portable. If it suddenly
| expects a disk write and I'm running on a ROM, I'm going
| to have a hard time. If it suddenly tries to contact
| google.com and I'm on an airgapped machine, I'm going to
| have a hard time. It's not even a matter of breaking the
| app actually, it's a matter of warning that "I'm going to
| go outside the realm of just your CPU and your memory".
| Sure, you could also mark as as async a function that
| adds two numbers. It's stupid, it's a JS thing to do, but
| you could. Except that you need to actively opt-in to
| being async in this case, as opposed to have it be forced
| upon you because you used a function that is also async.
|
| Blocking has meaning in a lot more contexts, and being a
| consultant on JVM related topics, you should know that:
| it's the entire purpose of Project Loom, and Loom doesn't
| entirely get rid of colour coding either explicitly for
| that purpose. Loom wasn't made because the guys at Oracle
| have a deep love for JavaFX, but rather takes into
| account the server world, where you really want to know
| that you're going into another context, another computer,
| etc. The only time where the existence of async doesn't
| make sense is if your entire language and ecosystem
| expects everything to already be distributed. In which
| case, you've just switched the default color to async.
|
| Finally, you chose to read async as the current JS/C#
| abomination implementation, but most of the sensible
| languages have implemented it as an effect: Kotlin has
| suspend funs, they don't return a Promise, but they tell
| you two things: they're going to touch something like the
| disk or the network, and if you really want to have them
| in a non-suspend context, you can either get a
| Deferred<T> out of them (and find another thread to run
| it on, and handle synchronization yourself), or run them
| on the current thread (and block everything).
| coldtea wrote:
| > _Adding a disk read won 't break my software, but it
| can certainly make my software non-portable. If it
| suddenly expects a disk write and I'm running on a ROM,
| I'm going to have a hard time. If it suddenly tries to
| contact google.com and I'm on an airgapped machine, I'm
| going to have a hard time. It's not even a matter of
| breaking the app actually, it's a matter of warning that
| "I'm going to go outside the realm of just your CPU and
| your memory". Sure, you could also mark as as async a
| function that adds two numbers. It's stupid, it's a JS
| thing to do, but you could._
|
| You're conflating too many orthogonal things.
|
| It's not merely because of the performance of touches
| disk/network that async was used for those cases, it's
| because that waiting is not because you're held up by the
| language doing calculations. That isn't the case with a
| function like you describe.
|
| Marking functions async when they aren't yielding just to
| signal that they might be slow is a bizzare idea. That's
| not what async is and it doesn't bring any real benefit,
| it's just abusing the notion (and limiting the contexts
| where you can run those functions). You'll still be using
| libraries which wont follow this strange idea, and you
| should know their performance characteristics.
|
| Blocking code exists in all major languages, including
| JS. In a single thread context, having something "async"
| wont help you at all, if it calls anything blocking,
| which can be something as common as JSON.parse
|
| > _If it suddenly expects a disk write and I 'm running
| on a ROM, I'm going to have a hard time. If it suddenly
| tries to contact google.com and I'm on an airgapped
| machine, I'm going to have a hard time._
|
| All of those have nothing to do with async, and what
| async is created and used for.
|
| What you want is something like Haskell's IO "tainting",
| a (side) effects system, or something to that (no pun
| intended) effect.
| mike_hearn wrote:
| Blocking in Loom is primarily about waiting for network
| traffic, possibly with extensions to file IO in future -
| it doesn't suspend if you do file IO today. Loom does get
| rid of coloring, or rather, doesn't introduce more of it
| and lets you phase out what exists, so I'm not sure what
| exactly you mean by that.
|
| Kotlin suspend funs do _not_ tell you they 're about to
| touch network disk, that's the reason they use "suspend"
| and not "async". Suspend funs can also use generators
| (with yield) in which case no I/O is happening but they
| are nonetheless suspending.
|
| So this is why blocking as a concept isn't a great one,
| IMO. The Kotlin designers were right to not use the word
| in their implementation of coroutines. Where Loom
| discusses blocking a bit, it's not defined as being
| _about_ blocking, it 's about being able to scale up
| threads to way beyond what was previously possible. It
| just so happens that the primary reason you'd want to do
| that is if you have lots of threads that spend lots of
| time waiting for things, but that doesn't automatically
| require network or disk access. For example you can use
| threads that spend all their time in Thread.sleep if you
| were writing an agent simulation.
| kaba0 wrote:
| > possibly with extensions to file IO in future - it
| doesn't suspend if you do file IO today
|
| Wasn't basically all of the JDK's file APIs rewritten to
| io_uring-like calls to support Loom? I have thought that
| IO was definitely something that Loom handled.
| mike_hearn wrote:
| Last I heard it was on the todo list, but maybe it got
| done since I last looked. You can do async IO without
| io_uring.
| anonymoushn wrote:
| This comment reads like it's from an alternate dimension
| where "calling read(2) marks functions as async" is a
| common language feature. Over here, read(2) can block
| forever and normal functions can call it in just about
| every language.
| kaba0 wrote:
| Virtual threads (like java's loom, erlang's and goroutines)
| are arguably that.
|
| In case of java there really is no compiler magic even,
| "just" runtime magic.
| pas wrote:
| how big is the overhead for this? and how much easier is
| working with virtual threads than Rust's async?
| winrid wrote:
| 1. Low enough that you don't need to worry for most web
| backend code and 2. Anything is easier than that. *XYZ is
| not Send* amiright?
|
| With Rust's async you have to worry about a different
| worker thread picking up the work after a context switch,
| which makes things complicated. Not so with Java.
| josephg wrote:
| Its certainly possible _with_ compiler magic. Zig does
| exactly that - where functions can be colorless and they
| work slightly differently in async and syncronous contexts,
| doing the obvious thing in both cases. I 'm not sure how
| well it works in practice (I haven't spent enough time with
| zig to know). I'd worry that bugs would creep in to
| whichever variant of the function you aren't exercising.
|
| Its a lovely design though.
|
| https://kristoff.it/blog/zig-colorblind-async-await/
| svieira wrote:
| _Unyielding_ describes the issues that arise when the
| author of the code doesn't have control of the
| transactionality / atomicity of their operations at the
| level they need to:
| https://glyph.twistedmatrix.com/2014/02/unyielding.html
|
| To my mind it's very much a balancing act between "low
| power to the developer, high power to the language" and
| "high power to the developer, low power to the language"
| all up and down the stack from software / hardware to
| "consumer / framework".
| svieira wrote:
| See also:
| https://www.tedinski.com/2018/11/06/concurrency-
| models.html which is the first article I've ever seen
| that says that `m:n`-style-green-threads are bad and good
| at the same time.
|
| TL;DR - he says that "doParallel" and "doConcurrently"
| are separate operations with distinct semantics that
| designers of programs must care about and that conflating
| the two (especially the common "doConcurrently-and-often-
| but-not-always-in-parallel") is one of the most common
| causes of bugs in programs that need to make progress in
| multiple threads of execution.
| creatonez wrote:
| Thing is, it's a function coloring that mostly doesn't
| impede you by making certain operations impossible without
| massive refactoring. Regardless of an extra method call
| needed, it gets rid of the "you can only call a red
| function from within another red function" barrier by
| allowing you to do it, as long as you specify how (execute
| inside this thread, or delegate to a worker thread). This
| makes most of the issues associated with Javascript
| function coloring evaporate.
| sph wrote:
| Fair enough. It's not a deal-breaker kind of coloring, my
| issue is mostly conceptual. It's simply not elegant as a
| construct, but perhaps it doesn't have to be.
| belmont_sup wrote:
| Algebraic effects like how OCaml's now implementing it may
| be possible! The ecosystem around effects in OCaml is still
| really young, but here's an example of an http request
| being made that is asynchronous, non-blocking but looks
| synchronous with no special syntax.
|
| https://github.com/mirage/ocaml-
| cohttp/blob/16e991ec1f7e5f0c...
|
| Performing these effects is similar to throwing exceptions
| up the callstack where whichever ancestor handles the IO
| work, then resumes the child with IO work done in hand.
| mjmahone17 wrote:
| As someone who likes OCaml, but hasn't touched OCaml in
| ~5 years, that's a very hard example to read. I can tell
| it's making a network request, but I have no clue:
|
| - what the type of res is (is it a string? A buffer?
| Async string? Something else?)
|
| - how this code does not block: what is happening in
| parallel? At which point will it block?
|
| I would like to be excited about OCaml's algebraic
| effects, but right now I don't really get it.
| yawaramin wrote:
| - It's some type from which you can read a response body.
| In OCaml we often work in terms of abstract types i.e.
| the operations which can be done on types. If you want to
| see the exact name of the type you can always look up the
| signature of `Client.get`:
| https://github.com/mirage/ocaml-
| cohttp/blob/16e991ec1f7e5f0c... (nowadays LSP support in
| any good editor will show the type on hover)
|
| - It's implementing non-blocking I/O using effect
| handlers. The complexity is not exposed to the library
| user, which is actually the whole point. If you want to
| dive into the concurrency library (Eio) and study how it
| works, that's very doable, just like any concurrency
| library in any other language.
|
| There's really not that much to get-OCaml will do async
| I/O without having to colour functions, just like Go,
| Erlang/Elixir, and soon Java. It's not something sexy
| like monads that excite people with mysterious FP
| concepts, it's just a lot of hard work that went into
| simplifying how developers code at scale.
| moth-fuzz wrote:
| You can't evaluate a future in normal rust as there's no
| default executor, you need to pull in some library to even
| make _blocking_ calls to async functions.
|
| IMO, this is even worse than function coloring.
| jarjoura wrote:
| It's the same in C++. The compiler and language design came
| first and then the library (aka executor) part comes next.
|
| Rust has basically 2 executor libraries, tokio and async-
| std. It seems to me like tokio is solidifying as the
| executor of choice and it's only a matter of time before
| that design is baked into the std library.
| weinzierl wrote:
| It's a bit strange indeed. On the other hand you can't
| allocate either if you don't have an allocator. The
| difference is just that the allocator is opt-out while the
| reactor and executor are opt-in with no formal default.
| zanellato19 wrote:
| You can also make your own executor. That's exactly how it
| was intended.
| moron4hire wrote:
| I've been using C# and (Java|Type)Script pretty much since
| they were invented. Both use a function coloring async
| system. I don't know what it's like in Rust, but at least in
| the two examples with which I have experience with this
| apparently much-maligned system, I really don't get the
| complaints. Having to "color" functions really isn't that big
| of a deal.
| z3t4 wrote:
| JS has first class functions that you can pass around. The
| different colors debate was just a confusion - there where
| only one type of function, just that some functions took a
| function as an argument, althgough JS has no async functions.
| All async functions in JS comes from the runtime, like
| addEventListener for web DOM events. The "solution" to the
| non existing problem the committie came up with was to
| introduce 3 new types of function. So JS went from one type
| of function, to four types of functions.
| divan wrote:
| It's crazy that the programming community even accepted the
| concept of async/await as a sane one.
|
| Being sync or async is essentially a property of the attention
| of the caller, not of the action itself. Is "eating a donut" a
| sync or async action? If I'm focusing all my attention on it,
| essentially putting all tasks aside (after) - then it's a
| synchronous action. If I'm reading a book/watching a
| video/walking/etc, while eating - it's an async action.
|
| How does "func EatADonut() async {}" aka "eating a donut is an
| inherently async action" even make sense to people?
| pmoleri wrote:
| > How does "func EatADonut() async {}" aka "eating a donut is
| an inherently async action" even make sense to people?
|
| Of course it does, read it as "beware, something blocking
| down the road".
|
| If you can EatADonout without blocking, please do, but want
| it or not that's a different implementation, one that doesn't
| block and the signature it's telling you so.
|
| We're so used to sync and having hidden blocking operations.
| I wonder if in an alternate universe the first languages
| considered the blocking/async nature of operations and then
| some newer languages considered hiding this information into
| seemingly sync functions would produce similar but opposite
| outrage against it.
| [deleted]
| divan wrote:
| > want it or not that's a different implementation,
|
| Implementation is the same. In both cases it's the same set
| of CPU instructions, but async/await languages create
| artifical division, forcing developer to think othervise.
|
| So, let me explian my reasoning. Code starts with a
| developer's mental model of a problem and behavior of the
| system and then translating it into the code. The more
| straightforward this translation, the more readable the
| code. Code is a second degree map of problem domain so to
| speak ("real" world -> mental model -> code).
|
| Like if you want to add two values, the simplest form of
| code would be "add(2,2)" which is pretty straightforward.
| If the code forces you to do some mental gymnastics (i.e.
| "2 2 addOnlyEvenNumbers") - that's less straightforward,
| less clear and less readable.
|
| In the same vein, if you want to execute some function
| ("EatADonut" or "MakeHTTPCall") - you may care or not care
| about blocking and waiting for results. But it's your call.
| So it makes sense to give you two options to run this
| function. Go has simplest possible solution - "eatADonut()"
| vs "go eatADonut()". It doesn't matter what is a "default"
| here - it could be "eatADonut()" (go to background) vs
| "sync eatADount()". What matters that "eating a donut" is
| just a set of instructions to the CPU, and it's up to
| caller to decide how you want to execute it in terms of
| concurrency.
|
| Now, "async/await" approach turned this ownership of
| "synchronicity" around. Now function is deciding how it
| should be called. Mental model of "actions" now needs to be
| translated into "actions being async or sync for the
| purpose of fitting into this language concept". Which is
| cognitively expensive for no added benefit.
|
| Sure you can rationalize it, and get used to it as to any
| other absurd design, but it still adds unneeded complexity
| to the code, makes it less readable and less clear.
| paulddraper wrote:
| IDK about donuts.
|
| But consider a network request. The vast majority of the time
| is not spent in the CPU.
|
| Right?
| divan wrote:
| Doesn't matter, donut was just an abstraction.
|
| s/eat a donut/make a network request/
|
| Is "network request" a synchronous or asynchronous
| activity? It depends whether your code blocks and wait's
| until response (or timeout) or continues executing and
| handling it when it comes. It's property of the "attention"
| of the caller, not of activity itself.
| joshuamorton wrote:
| But "can be gracefully executed asynchronously" is a
| property of an action.
|
| Something that pegs the CPU to 100% because it's doing
| intense processing isn't a good candidate for async.
| Similarly, some code can cause issues when written in a
| non-streaming fashion. Take the following example (in
| python): x = [x for x in
| range(1_000_000_000)] y = (x for x in
| range(1_000_000_000))
|
| If you spawn a bunch of async workers doing the first one
| concurrently, you'll OOM your system. If you spawn a
| bunch of async workers doing the second one concurrently,
| your system will be fine.
|
| In other words, "async" is a label on a box of donuts
| that implies (though doesn't ensure, people can of course
| still do bad things) that the donut won't explode if you
| look away from it.
| divan wrote:
| Hmm, is it a post-factum rationalization or it's the
| original logic behind async/await? Let's mark "heavy"
| functions with a label, so user has to call them
| differently not to overload the system?
|
| Even if this is an original logic, why language is
| deciding for me what is considered heavy or not? What if
| I'm fully aware that I'm doing heavy processing and I
| want it to be happening in the background. What if I'm
| writing HFT software and every call is heavy for me.
| Language is not a right level of abstraction of marking
| "heaviness" of the code.
|
| It really just doesn't make sense. Why stop there and not
| start marking functions with how many times per second
| they can be called? Like you can call "light" function
| 100 trillion times per second and OOM the system, so
| let's mark it "func light() async 2_times_per_second {}".
| It only can be called from functions that have lower per
| second label.
|
| Plus, if Python cares so much about not OOM-ing the
| system I would start by not requiring 32-bit int to
| occupy 28 bytes in the first place.
| wizofaus wrote:
| It's only functions that "block and wait" that it makes
| sense to be async - the point being that blocking and
| waiting doesn't use up CPU cycles, so you might as well
| free the thread up to do other stuff. If a function
| issues a socket write, then continues utilising the CPU
| while also periodically checking if data's available to
| read, it's effectively doing manually what async does for
| you, though I'm not sure there are too many real life
| examples of such functions. But pre-async it was
| certainly common enough for block-and-wait functions to
| tie up a thread and hence execution of programs with
| limited multi-threading (even today, GUIs often require
| all events and updates to be processed on the primary
| thread).
| bourneavent wrote:
| >How does "func EatADonut() async {}" aka "eating a donut is
| an inherently async action" even make sense to people?
|
| You have to think of it from a higher level.
|
| Nobody knows if eatdonut is strictly async but everyone knows
| all IO calls are async. So something like socket.send would
| be async, this is obvious.
|
| Then anything that calls socket.send would then in turn
| become async itself. The async sort of spreads around like a
| virus. Any function that uses other async functions gets
| polluted with the async marker.
|
| So in this sense, you should think of it like this. If
| eatADonut is async it means it touches IO. It sends data
| somewhere else to some other thing. It also means it's not
| unit testable.
|
| In a nut shell This is the intuition you should have about
| async: Async Tells You about the properties
| of eatADonut. eatADonut doesn't tell you anything about
| whether or not it's async.
|
| This is largely identical to how the IO monad works in
| Haskell. Unless you want all your logic polluted with the IO
| monadic wrapper or the async marker you have to learn to
| segregate your functions into async functions and non-async
| functions.
|
| Under this intuition, EatADonut, by good design principles
| should NOT be async. This is what it should be:
| fn eatAdonut() -> ResultOfEatADonut async fn
| sendDataToIO(data: Data) -> () async
| parentCaller() -> () { let data = eatAdonut();
| sendDataToIO(data.serialize()).await; () }
|
| The async marker forces you to organize your code in this
| way, You have to do this unless you want all your functions
| to be polluted by the async marker. This is the proper way to
| organize code Anyway, as it allows your code to be unit
| testable. Async calls are, in general, not unit testable
| because the functions are not pure. So if you look at what I
| did above, I segregated eatAdonut Away from IO and made part
| of the logic of the overall code testable via unit tests.
|
| IO calls should be very general and small functions while
| your other functions should be pure and unit testable. This
| is the overall intuition you should have about async.
|
| Believe it or not, this specific aspect of async is actually
| an improvement over golang style concurrency. Not an overall
| improvement, it's still debatable which style is better, but
| I'm saying the async marker is actually a feature that other
| styles of implementing concurrency don't offer.
|
| Basically, async encodes the concept of IO and function
| purity into the type system. It allows the type checker and
| You to tell which functions touch IO and which functions are
| pure and unit testable.
|
| People think unit testability is some kind of marginal
| benefit because you can cover testing with integration tests
| which are basically tests that happen across IO. It's more
| than that. The ultimate dream of programming something as if
| it's a bunch of modular lego blocks that you can elegantly
| place together is most fully realized with pure functions.
| Async enables more of this style of modularity in Rust (but
| not without some cost which is why it's debatable whether or
| not it's better than go style concurrency).
| kmac_ wrote:
| How about waiting until all or part of donut pack is eaten by
| multiple eaters? Jokes aside, async/await comes from easier
| handling of a callback code and automatic function splitting.
| Writing asynchronous donut eating in loops or with yielding
| of partial results is very easy to swallow with such
| syntactic sugar. The whole model is very easy to grasp and to
| work with. I'm not saying that's the best solution but it
| definitely works well.
|
| Regarding stickiness of function colors - it never happens
| when async/await is used correctly (in the same principle as
| IO monad isn't sticky).
| divan wrote:
| Doesn't matter. You either wait until donut is eaten by
| others or you don't. Caller is deciding if it's
| "synchronous" (blocking) action or not.
|
| The question is how you translate mental models from real
| world to the code, and async/await fails here
| spectacularly. It's just weirdly unnatural thinking about
| sequencial processes. It requires tremendous amount of
| cognitive gymnastics just to reason about simple things.
| bmacho wrote:
| This was my first (and obviously wrong) mental model of how
| async works.
|
| There are functions, you can call them sync or async if they
| handle IO or UI or they will get a necessary data later.
|
| I still don't understand why I can't fetch a URL from top
| level javascript. Also I don't understand why zig async and
| await passes the control flow that seemingly total arbitrary
| way. The naive approach (put async calls in a queue, and
| periodically check if they are completed or can be executed)
| seems fast, deterministic, and good enough in every way?
|
| Okay, maybe zig needs the speed, and can't just stop the
| execution of the sync code time to time to do something else,
| but why javascript? Maybe javascript async is build upon
| regular promises and regular objects, instead as a proper
| language element with proper support in the javascript
| engines? I don't know. Anyway async as is used with colors is
| totally against the picture that the words "async" and
| "await" suggest.
| jaxrtech wrote:
| If Rust was a higher-level language, then I'd say yes, just
| automatically handle running Futures in parallel, join them
| when you actually need to resolve the data, and pretend that
| they look like synchronous functions in the 80% of cases.
| Though things like `select!` wouldn't make any sense mixing the
| two.
|
| I continue to find the "function coloring" argument misses the
| point unless you're arguing from a developer experience
| perspective. Why should two fundamentally different things look
| and function the same? Want this the ultimate pitfall of early
| RPC implementations where everything looks synchronous?
|
| In Rust, a lot of the friction is due to how async
| functions/futures fundamentally differ in model of execution
| and how that interplays with the rest of the language. Other
| languages get to hand-wave a lot of these problems away with a
| GC. (It certainly could be less of a pain nonetheless.)
|
| - Futures don't execute until polled, can partially execute if
| something isn't ready (and would block), can partially execute
| and be arbitrary cancelled and dropped. There is no equivalent
| in sync functions unless you make them co-routines.
|
| - Since you can "pause" futures if something would block, you
| need a place to store the function's state for when it's
| resumed. Now you must be consider if the async function's state
| is `Send` if the future wants to move to a different thread to
| continue executing -- which is why you see `pin!` used. Sync
| functions don't care about this since you always run to
| completion on the same thread, and the stack is ephemeral.
|
| - Likewise, the `Future` returned by the async function is
| going to need to encapsulate it's state that it saves. In the
| general case, this is a compiler generated anonymous struct
| that changes if any state saved across `.await` changes, hence
| the opaque `impl Future`. This is why you see `BoxedFuture` a
| lot to abstract this away at the expense of an allocation.
| Ideally, the new associated types with lifetimes can avoid this
| with traits.
|
| So if all functions were co-routines (i.e. functions that can
| be resumed and re-entered) they would all have the same
| "color". But all you really did was "lift" all sync functions
| to be "async" functions with no internal await points.
|
| (IMHO, if the C# team back in the day decided to implement full
| blown co-routines into the language instead of just
| `async/await` as a compiler trick, I think many other projects
| would have followed suit with a more general co-routine
| solution instead of treating `async/await` as this special
| thing which is just a specific compiler generated
| implementation of co-routines.)
| dist1ll wrote:
| > That said, I still hate async with a passion, it makes the
| language more complex and not very elegant (i.e. function
| coloring)
|
| Function coloring seems to come up a lot in these discussions,
| but I don't see a better way without providing a runtime. Could
| you propose an alternative approach to async, without
| sacrificing ability to write zero-overhead, high-performance
| bare metal systems?
| umanwizard wrote:
| They could have simply not added async to the language. I was
| happily using rust before async and it was perfectly fine.
| hgomersall wrote:
| So don't use it. Why object to those that do have a use
| case for it?
| umanwizard wrote:
| Because I have to work with other people (both coworkers
| and developers of library crates).
| ninkendo wrote:
| Because it's possible the crate you want to use, uses it.
| hgomersall wrote:
| Or alternatively the crate you want to use doesn't exist.
| ay wrote:
| fragmentation of the library ecosystem.
| bsder wrote:
| > That said, I still hate async with a passion, it makes the
| language more complex and not very elegant (i.e. function
| coloring).
|
| async in Rust is a popularity issue even more than a technical
| issue.
|
| A whopping amount of people who are coming to Rust are doing so
| because they want a good _ecosystem_ for implementing network
| service servers. Rust /cargo/crates hits the sweet spot for
| them.
|
| I am with you that I loathe async/await in Rust. However, I
| also have to acknowledge that without async/await, Rust is a
| _vastly_ less popular language.
|
| All the other uses of systems programming are simply _dwarfed_
| by number of people building network services. That 's just the
| sad reality.
| fzeindl wrote:
| I hate async as well. Developers should have learned about
| communicating sequential processes and blocking queues and none
| of that would have been necessary. It creates a weird divide in
| every language. Just learn about threading and do it.
|
| A nice talk about this:
| https://www.reddit.com/r/programming/comments/da141r/ron_pre...
| deltaninenine wrote:
| [dead]
| unscaled wrote:
| I'm pretty sure the designers of Rust knows about CSP. Rust
| has Channels in the standard library after all:
| https://doc.rust-lang.org/rust-by-
| example/std_misc/channels....
|
| Rust Channel have the nice additional that they don't panic,
| and don't have all the weird Go Channel Axioms[1] that you
| have to wrap your head around to get things to work
| correctly[2].
|
| For what it's worth, I don't think Java Virtual Threads
| (a.k.a. project loom) has implemented an explicit concept of
| channels, although I guess BlockingQueue could work as a
| channel.
|
| There is a big issue with bringing CSP to languages like Java
| 21 or Go, that use so-called "colorless concurrency" or
| "stackful coroutines". CSP losses a lot of its modeling power
| when you combin shared mutable state and pre-emptive (or
| invisible) parallelism. And this is exactly what both of
| these languages do.
|
| "Colored" abstractions like async/await are more cumbersome
| (especially in Rust), but they let you know exactly at which
| points of execution your stackless coroutine may suspend, and
| you can handle synchronization accordingly.
|
| Those functional concepts are cumbersome, but threads
| ("green", "kernel", "virtual", "goroutines" or otherwise) are
| not the panacea their proponents claim them to be. I had to
| debug way too many data races in my life to consider them
| perfect. Rust still has a lot to improve on its async front
| (no async traits, async closures or async iterators and the
| async runtime confusion), it can at least promise me that I
| won't get any data races.
|
| [1] https://dave.cheney.net/2014/03/19/channel-axioms [2]
| https://www.jtolio.com/2016/03/go-channels-are-bad-and-
| you-s...
| duped wrote:
| Developers should learn the difference between concurrency
| and parallelism because async isn't equivalent to threading.
| Freedom2 wrote:
| Yep, it's pretty alarming that a developer would toss away
| async when threading isn't a fit for all situations - what
| happened to the right person tool for the job? As a
| professional, I 'hate' some things too, but I'm not going
| to blindly make my work harder and less efficient out of
| some small preference I have.
| tomjakubowski wrote:
| async has fragmented the crates ecosystem. If you want to
| want to write async-free code with threads, you'll
| probably still have to opt-in to an async executor for
| some dependency because these days many libraries will
| implement only an async interface
| duped wrote:
| I think you're manifesting what I'm talking about. The
| reason you cannot replace async with threads has nothing
| to do with the ecosystem, but the simple fact that
| threading is a model of parallelism and async/await is a
| model of concurrency. These are two subtly different
| concepts in software architecture, but they are not the
| same thing.
|
| More concretely, threaded code has nothing to say about
| whether or not its synchronous or asynchronous, and
| asynchronous code has nothing to say about whether or not
| it's evaluated in parallel.
| fzeindl wrote:
| > I think you're manifesting what I'm talking about. The
| reason you cannot replace async with threads has nothing
| to do with the ecosystem, but the simple fact that
| threading is a model of parallelism and async/await is a
| model of concurrency.
|
| That is an artificial distinction you made up. I have
| written many apps and desktop applications, all of which
| where using 3-10 threads, some of them doing "parallel"
| work meaning the parallel computation of divide and
| conquer algorithms, some were doing concurrent work.
|
| I tried async often. I threw it away every single time,
| because it "infects" the entire codebase. I suspect the
| only reason async exists is because the Javascript event-
| loop is single-threaded and there are no blocking
| primitives in JS. If you don't have access to blocking
| data-handoff between threads, you can choose between
| callback hell or async/await.
|
| Please take a look at the video I posted earlier.
| magicalhippo wrote:
| https://towardsdatascience.com/concurrency-and-
| parallelism-w...
|
| Besides IO Completion Ports for async IO has been the
| fastest way to do heavy IO on Windows, and it was
| introduced years before JavaScript even was a thing.
|
| And that's just one example. So hardly think JavaScript
| was the main driver there.
| singingboyo wrote:
| It's not an artificial distinction at all, but it is
| subtle.
|
| 2, 4, even 256 sockets exchanging data can be worked with
| concurrently on a single thread and gain performance vs
| blocking and waiting for the first socket to finish.
| There's no parallelism, since they're never actively
| reading/writing at the same time, but they're concurrent
| because they exist and operate in overlapping timeframes.
|
| Running two independent algorithms could mimic this - run
| part of algo A, then part of B, then A, etc. It's not
| useful for performance, though. To be useful you require
| parallelism - you have to have the algorithms executing
| at the same time, using multiple threads.
|
| On async itself - it's not perfect, but honestly, there
| are contexts where it makes a lot of sense. I work on a
| lot of non-blocking C code - the entire programs are
| basically epoll and timer driven. As a result, the high
| level coordination is just callback hell. Async is very
| nice in comparison.
|
| Yes, you can spin up threads to do everything with
| blocking, but non-blocking I/O came around specifically
| because the threads add overhead and kind of suck. It's
| worth noting that having threads can also infect the
| codebase. You either have to carefully manage mutexes, or
| you only communicate with channels and have to worry
| about keeping things updated and in sync. Sometimes this
| works great with minimal communication between threads.
| However, if you have one socket per thread and the
| sockets are all triggering actions that mess with the
| same data... it's not so great.
| deltaninenine wrote:
| [dead]
| davedx wrote:
| That being said... python had a BDFL and look how that turned
| out.
|
| I think designing and evolving any living programming language
| is just one of the hardest problems out there.
|
| Incredible blog post indeed, was awesome to read it.
| bazoom42 wrote:
| > That being said... python had a BDFL and look how that
| turned out.
|
| One of the most popular and succesful languages, and a major
| force in AI innovation?
| _joel wrote:
| With a dodgy 2->3 migration pathway and far too many
| package managers.
| 015a wrote:
| And _despite_ that, still a top 3 most popular
| programming language on the planet. And another in that
| top 3 is the universally-derided Javascript.
|
| There's a lesson in there to learn. Its unfortunate that
| the people who need to learn it most likely won't.
| LordDragonfang wrote:
| It's important to note that JS has an unfair advantage in
| that if you want to write code that runs on the universal
| platform, you're forced to use either it or something
| that transpiles to it. Python reached where it was on
| merits alone.
|
| (That said, JS is actually a very versatile and almost-
| great language, getting better all the time)
| timschmidt wrote:
| Python reached where it is because university CS programs
| which had previously been teaching Java reached for a new
| language.
| viscanti wrote:
| The py2 to py3 transition seems to have gone very poorly,
| although most of the pain from that is behind us now.
| jefftk wrote:
| That was a seriously bad call, but it was a single one
| and he's made a ton of other decisions over the years.
| rirze wrote:
| who's "he"?
| wez470 wrote:
| https://en.m.wikipedia.org/wiki/Guido_van_Rossum
| estebank wrote:
| Guido van Rossum
| thiht wrote:
| Honestly I blame the community.
|
| The transition was slow (Python 2 was supported in
| parallel to 3 for a long period of time so there was more
| than enough time to migrate), relatively easy to do, and
| brought huge benefits to the ecosystem.
|
| It could have been done and forgotten in 2 years if some
| community members had not been dicks about it.
| viscanti wrote:
| > Honestly I blame the community.
|
| Sure. The point is that a community can make things go
| poorly, with or without a BDFL.
| svieira wrote:
| It really seemed that the issue was mostly "Unicode is
| really hard if you haven't thought _deeply_ about it"
| because application code, library and framework code,
| _and_ "system" Python code had to go through several
| iterations of "here's how to do it right" before
| something reasonable came out that solved issues for most
| / all stakeholders. And the Swift / Raku people are still
| sitting on the sidelines smiling, knowing that there are
| a lot more "lenses" to view text through that Python
| doesn't make easy (but neither does Haskell, JS, or Java,
| so there's good company in good-enough-for-now land).
| hajile wrote:
| I blame a lack of tools.
|
| JS devs have been transpiling for years now and it works.
|
| Python should have released a conversion tool that could
| automatically convert most stuff and identify the stuff
| it couldn't convert. This kind of directed upgrade would
| have made the process much easier for developers to
| actually accomplish.
| LordDragonfang wrote:
| I'm confused, because isn't that exactly what they did?
| What was wrong with 2to3?
|
| https://docs.python.org/3/library/2to3.html
| hajile wrote:
| It didn't work well at all in my experience. Given how
| much code was thrown out rather than ported, I'm guessing
| that I'm not alone.
| imtringued wrote:
| Most of the breaking changes did not justify upgrading.
| collinmanderson wrote:
| > The transition was slow
|
| Here's the timeline the way I think about it:
|
| - 2008-2012 Python 3 becoming usable (byte formatting,
| six library)
|
| - 2012-2016 Libraries (Django, etc) becoming compatible
|
| - 2016-today Applications (Trac, Ansible, Chrome, etc)
| becoming compatible.
|
| It took a lot of time for the ecosystem to be ready. Yes,
| there was a lot of stubbornness, but that's reality.
|
| There are many things that could have been done to make
| the transition go smoother.
| sph wrote:
| A dictator might just have terrible ideas.
|
| His dislike for functional constructs didn't do Python any
| favour, but now that he's gone I see Python is adding the
| kitchen sink as well. I haven't kept up with the language
| since that pattern matching proposal.
| pclmulqdq wrote:
| People who are deeply into programming languages seem to
| like FP too much. That and theories of type systems. Python
| is doing some great things, but also just fucking around a
| lot.
| imtringued wrote:
| As someone who doesn't like Python. Python's problems
| have almost nothing to do the lack of functional
| programming. It does have the worst lambda syntax of any
| programming language but that is about it.
| mananaysiempre wrote:
| > [Async/await] feels so complicated and hacky, a mediocre very
| high level concept that someone managed to implement as a zero-
| cost abstraction.
|
| I feel like I need to point out that Donald "Structured
| programming with GO TO statements" Knuth included an example of
| coroutines in the first volume of _The art of computer
| programming_ , the first edition, dated 1968. In assembly
| language for an accumulator machine. With a box of
| scraps!^W^W^W^W^WThat is to say, that C and most other
| languages have made coroutines awkward and thus virtually
| unused in the past three decades or so does not mean they are
| particularly novel or high-level.
|
| Granted, Knuth used coroutines in a simulation and not for I/O,
| so he did not need that much of a scheduler, but still.
| logicchains wrote:
| The Rust async story would be much nicer if they'd put in the
| hard work up front to support higher-kinded types, as then it
| could have a monadic async API like OCaml or Haskell. I don't
| know anyone who's used async in both Rust and Haskell who
| prefers the Rust approach. It'd also fix oddities like why it's
| possible to write a function like the following in C++ but not
| Rust: template<template<typename> class
| MyContainer> int getFirstInt(const MyContainer<int>&
| myContainer) { return myContainer[0]; }
|
| (In Rust it's not possible to write something like
| MyContainer<int>; only the int is allowed to be generic).
| chrismorgan wrote:
| Are you aware of _generic associated types_ , which landed in
| 1.65 (late last year)? To a considerable extent, they're
| Rust's answer to higher-kinded types. Less expressive in some
| ways, but fitting Rust _way_ better, as basically a
| relaxation of a former restriction, rather than a new
| feature. If I'm reading your snippet right, GATs let you
| express exactly that, though minus `template`'s duck-typiness
| so you'd have to spell out what contract the container must
| adhere to.
| logicchains wrote:
| >less expressive in some ways, but fitting Rust way better,
| as basically a relaxation of a former restriction, rather
| than a new feature
|
| They're not expressive enough to implement a monadic async
| library, as far as I'm aware, so they don't fulfill the
| role of proper HKTs in this context.
|
| >minus `template`'s duck-typiness so you'd have to spell
| out what contract the container must adhere to.
|
| Do you think it's consistent that the T in MyContainer<T>
| can be "duck typed" but not the MyContainer?
| zozbot234 wrote:
| > They're not expressive enough to implement a monadic
| async library, as far as I'm aware
|
| AIUI you need GC in order to do that. There's a reason
| why Rust's async/await support needs compiler magic.
|
| (I'd like to see standard interfaces for "pluggable"
| garbage-collected arenas in Rust, but this will need to
| wait until after Allocators/Storages get fully
| stabilized.)
| estebank wrote:
| In Rust neither is duck typed. For T you must specify the
| traits it must have. This makes compilation fail at
| definition, not at the point of incorrect usage.
|
| For an example of GATs, you can see the following:
|
| https://play.rust-
| lang.org/?version=stable&mode=debug&editio...
| TurboHaskal wrote:
| > I too would have loved something more towards ML than C++
|
| I stopped paying attention to the language around 2012-2013 or
| so when the direction clearly steered towards the latter.
|
| You may want to check https://austral-lang.org/ out.
| bpbp-mango wrote:
| null type :(
| dorian-graph wrote:
| I was about to ask if there's any "Rust by ML" languages out
| there. Thank you.
| noelwelsh wrote:
| O'Caml is heading in that direction. Jane Street are adding
| more control over memory layout and allocation, and
| multicore finally arrived recently.
| TylerE wrote:
| Nim is the closest to what I had originally hoped Rust would
| be. Especially Nim 2 with the new GC is really exciting. I
| suspect it will outperform all but the best hand tuned
| allocators.
|
| It's even hard real time capable, which makes it potentially
| viable in scenarios like games where GC traditionally hasn't
| been.
| osigurdson wrote:
| I think a big part of any successful endeavour is alignment of
| authority, responsibility and competence. The problem with
| group decision making is responsibility is very diluted. If a
| bad decision is made, no one in particular is responsible.
| hardwaregeek wrote:
| A lot of Graydon's ideas feel like interesting extensions to ML-
| style languages. I bet if he had continued down that path, it
| would have been a lot more of an experimental language with a
| hodgepodge of different ideas. Which is totally valid (you need
| these languages to test new paradigms and features), but
| definitely would not have become mainstream.
|
| Basically, I view Grayson as a leader who set the tone for Rust
| being a language that was willing to take ambitious swings on
| cutting edge features. But I don't think he would have been the
| person to eventually make the cuts and compromises necessary to
| hew the language into a cohesive, mainstream language. Rust
| ending up as a replacement C++ helped it not only determine which
| features to keep and which rules to follow, but also helped it
| create the right pitch for developers to use it.
|
| This does lead to a larger question about BDFLs. Perhaps, like
| CEOs, the BDFL you want when you're starting a language is not
| the BDFL you want when you're maturing a language, or maintaining
| a language. Especially around feature selection, in the beginning
| it may pay off to add a lot of features based on user feedback,
| but later on it may be better to push back more. And from a
| psychological standpoint, I have wondered about the pressure of
| being a BDFL. Grayson has been open about stepping down partially
| due to reaching his limits, and I suspect other BDFLs have
| thought about it too. The job sounds exhausting and thankless. At
| a certain point, wouldn't you want to leave and start a new
| project? And wouldn't we want the person who had success once to
| give it another shot?
| dataflow wrote:
| > Rust ending up as a replacement C++ helped it
|
| If anything, it's more a C replacement than a C++ replacement.
| It will take some market share from both of course (and other
| languages to a lesser extent too), but functionality-wise, it
| just isn't (currently, at least) practically able to replace
| some C++ use cases.
| 59nadir wrote:
| In some ways I think Rust is a bad replacement for both. It's
| an alternative in some ways to C and an alternative in some
| ways to C++.
|
| I don't think anyone who still uses C today and hasn't lived
| in a cave for the last 20 years would be very interested in
| Rust since it's just not at all like using C. There is the
| rare Bryan Cantrill who for some reason was seemingly unaware
| that other languages existed from 1990 until 2018 but I think
| it's safe to say most other people who primarily use C would
| not prefer the leap to something like Rust.
|
| For the people who use C++, certainly they're already used to
| a language that wants to dominate, so Rust should be fine. In
| terms of features, apparently it doesn't hold up, but because
| it's like C++ I'm sure they're just a couple of years away
| from adding those things too, and then the language can
| continue being more important than the actual data
| transformations the programs are supposed to be doing.
| dataflow wrote:
| Yeah to some extent I agree. Regarding this:
|
| > but because it's like C++ I'm sure they're just a couple
| of years away from adding those things too
|
| If they're smart (which they are), I'm sure they'll
| eventually cave and add some of the missing stuff, no
| matter how much they want to believe these features are
| unnecessary. Just like how C is finally coming around and
| adding generics and all that. The particular capabilities I
| mentioned here wouldn't be impossible to add, and they can
| probably achieve some of them better than C++ did. But I do
| think there will remain use cases that Rust will fail to
| accommodate.
| badrequest wrote:
| I'm interested in more on this. What can I do in C++ that I
| can't do in Rust?
| TremendousJudge wrote:
| Write unsafe code wrapped in layers of those advanced
| features that half the team didn't even know existed, and
| push it into production.
| vanderZwan wrote:
| Am I the only one who works at a place where things don't
| make it into production unless at least one other person
| on the team fully understands the code during review?
| TremendousJudge wrote:
| Plenty of people don't, it's the most common thing. Maybe
| right now you are lucky enough to work in good contitions
| (that's great!), but are you sure it couldn't happen to
| you in 10 years? I think the standard tools we use in our
| profession should work in less than ideal conditions as
| well.
| dataflow wrote:
| This is just one example, but moves are always bitwise,
| correct? So if I want an object to track all users of it in
| C++, I can just make a smart pointer for it whose
| copy/move/destruction operations notify the object about
| each event. How would you do that in Rust? (Similarly, what
| if I want a relative pointer?)
| remram wrote:
| You can track all of this (things like Rc or Arc do)
| except move.
|
| Relative pointers are possible, depending on what you
| mean. Making this safe (e.g. preventing users of that
| type from breaking the relative addressing) is done via
| the Pin type.
| dataflow wrote:
| > You can track all of this (things like Rc or Arc do)
| except move.
|
| Which means you can't track this. Tracking moves is
| fundamental here.
|
| > Relative pointers are possible, depending on what you
| mean.
|
| Pretty sure they're not possible in the sense I mean, for
| the same reason as above - you need custom moves for
| this. I'm referring to a pointer (not an offset; a
| pointer) that automatically adjusts itself when copied or
| moved. So that it always points somewhere N blocks
| before/after itself.
|
| These are just two examples, to get the point across that
| Rust actually lacks some capabilities (since somehow that
| surprises people). You can find more.
| remram wrote:
| > Tracking moves is fundamental here.
|
| I mean, arguably. If it is fundamental for your
| hypothetical use case, then sure, but this is not
| required for a lot of use cases, like smart pointers.
| dataflow wrote:
| > this is not required for a lot of use cases, like smart
| pointers.
|
| Nobody claimed otherwise. The question was what things
| Rust can't do, not what it can do.
| steveklabnik wrote:
| (Not your parent) Many people point to more advanced meta
| programming, such as variadrics, higher kinded types
| (though as mentioned GATs advance Rust in this general
| area), and specialization.
| jstx1 wrote:
| A bunch of things you don't like about Rust? Turns out that the
| person who originally created the language doesn't like them
| either.
|
| I know that the main point is about governance and how having a
| BDFL would have led to a completely different language but I
| really would have preferred the Graydon-BDFL-Rust to what we have
| today.
|
| Very interesting article, worth a read.
| lamontcg wrote:
| I think I mostly want a language like Graydon-Rust, but not
| with the Expressivity tradeoff (or at least not as hardcore as
| it sounds like Graydon is).
| shoulderfake wrote:
| [dead]
| avgcorrection wrote:
| > A bunch of things you don't like about Rust? Turns out that
| the person who originally created the language doesn't like
| them either.
|
| Yep. That just goes to show that you shouldn't put individuals
| on pedestals as if they are superheroes. More things than we
| usually think are in fact team efforts.
|
| > I know that the main point is about governance and how having
| a BDFL would have led to a completely different language
|
| That isn't the only _main point_.
|
| > > The Rust I Wanted probably had no future, or at least not
| one anywhere near as good as The Rust We Got.
|
| If you would be fine with a niche language then that "Rust"
| would have worked for you. But if you also wanted a language
| with wide industry backin etc.--maybe not so much.
| elcritch wrote:
| One highlight from the article:
|
| > Traits. I generally don't like traits / typeclasses. To my
| eyes they're too type-directed, too global, too much like
| programming and debugging a distributed set of invisible
| inference rules, and produce too much coupling between
| libraries in terms of what is or isn't allowed to maintain
| coherence.
|
| Very much this!
|
| > I wanted (and got part way into building) a first class
| module system in the ML tradition. Many team members objected
| because these systems are more verbose, often painfully so, and
| I lost the argument. But I still don't like the result, and I
| would probably have backed the experiment out "if I'd been
| BDFL".
|
| I seen this the ML first class module system mentioned in a few
| different contexts and want to pick it up at some point.
| wuiheerfoj wrote:
| May I ask what you don't like about traits (and perhaps
| insight on what the author meant)?
|
| Being a relative rust noob compared to other languages, I
| always felt traits were a super power when compared to eg
| interfaces in Java/C++ and flexible but with useful
| constraints compared to the structural typing nature of
| Typescript interfaces
| nyanpasu64 wrote:
| Importing a trait into scope can silently materialize
| methods on other types, with no syntax at the method call
| site pointing to which import statement created the
| methods, requiring you to ask the compiler/IDE. This can
| also mean that removing a use statement with no references
| to the type being used in the entire rest of the source
| file, can make code suddenly stop compiling. I've worked
| around this by strictly confining "specialized" traits
| (like std::io::Write or std::fmt::Write, why are there two
| Writes?!) to the beginning of a single function, but I'm
| lazy and put "general" traits like std::borrow::Borrow and
| std::str::FromStr at the top of my file.
|
| Which types grow new trait methods is dependent on trait
| implementations, which can be generic and apply to an
| unbounded number of types, based on complex matching rules,
| and figuring out requires reading every impl of that trait
| to see if any match a given type, or asking the
| compiler/IDE. I want to explore languages which explicitly
| select a trait implementation at the call site (like a
| Heap<int, int_greater> type which uses int_greater::cmp()
| to implement a min/max heap), or naming the trait (but not
| picking an impl) at the trait method call site (like
| Write::write_all(stream, "hello world") or (stream as
| Write).write_all("hello world")). I think Zig takes a
| similar direction.
| zozbot234 wrote:
| You can use universal function call syntax in Rust:
| <Type<TyArg> as Trait>::trait_fn(self_arg, fn_arg1,
| fn_arg2);
| glandium wrote:
| There's worse. When your trait adds function foo and your
| code does obj.foo(), if the underlying type later adds a
| foo method, compilation breaks. This is very common with
| traits that usefully add useful methods that are missing
| in libstd... which break when said method is finally
| added there.
| Gwypaas wrote:
| I ran into this case yesterday, for some cases there are
| warnings now. In this case I used ".div_ceil(...)" from
| from the Num [0] crate.
| (some_integer).div_ceil(&2)
| ^^^^^^^^ = warning: once this associated
| item is added to the standard library, the ambiguity may
| cause an error or change in behavior! = note:
| for more information, see issue #48919
| <https://github.com/rust-lang/rust/issues/48919>
| = help: call with fully qualified syntax
| `num::Integer::div_ceil(...)` to keep using the current
| method = note:
| `#[warn(unstable_name_collisions)]` on by default
|
| [0]: https://crates.io/crates/num
| glandium wrote:
| This warning happens during the time the method is added
| to libstd but is still unstable.
| Gwypaas wrote:
| Given that all important codebases should have CI that
| rejects warnings and runs clippy I would say it is
| acceptable.
| sanxiyn wrote:
| In a trait-like system (including Java interface), there
| can be only one implementation of a trait for a type.
| That's why it is global, unlike ML module-like systems.
| Global is another word for anti-modular.
|
| For example, in the real world, there are multiple ways to
| order strings (called collation), but since there can be
| only one implementation of (String, Ord) type-trait pair,
| one ordering is canonical. That may be bearable, but having
| canonical (String, Hash) implementation is not. What if you
| want to use (say) faster CityHash instead of canonical
| MurmurHash? So Rust resorts to things like BuildHasher, to
| get back multiple implementations.
|
| Because it is global/anti-modular, it interferes with
| separate compilation, and it is one of reasons why Rust is
| slow to compile.
|
| Then why would one use trait instead of module? Since there
| can be multiple implementations in module, you need to
| specify. So module is more verbose. In my (and Graydon's)
| opinion, a bit more verbosity is worth it for modularity,
| but many people disagreed.
|
| This is another theme: Graydon is okay with verbosity,
| boilerplate, and being bureaucratic. In my (and Graydon's)
| opinion, programming is work that is secretarial, not
| artistic, so it is unimportant whether code is ugly or not.
| You may think current Rust is ugly, but no, it is the way
| it is because lots of people really cared about Rust code
| being pretty. If Graydon was a BDFL, Rust would be even
| more ugly, and in my opinion, as a result, would be a
| better programming language.
| dan-robertson wrote:
| I often felt that being canonical was a pretty big advantage
| of traits. For example if you use first-class modules then
| every time you want a sort or a binary search tree you need
| to specify the choice of comparison operator for the
| elements. And a data structure for a search tree must somehow
| encode this in the type system so eg you can't merge two
| trees with the same key type but different comparison
| functions. The most obvious way to implement this causes
| problems for other cases like writing functions generic in
| the types of keys/values (else you get extremely verbose
| code).
|
| Canonical instances goes against modularity but something
| like modular implicits for ML-family languages could improve
| the terse was a lot to something closer to Haskell levels.
| You still miss out on the other advantage rust has: the dot
| operator is great because it allows identifiers to contain
| less context (they don't need to have long global names or be
| hidden away in some tree of modules: you can have .map mean
| different things for different types) and gives a massive
| hint to autocomplete for which things to suggest (I don't
| know how you even signal to autocomplete in a language like
| ML that you would like a function to operate on the following
| value so please suggest things with the right type).
| zozbot234 wrote:
| Most type systems are not full-featured enough to elegantly
| encode traits/typeclasses or ML modules. You need dependent
| types, as seen in languages like Agda or Idris. And these
| in turn are hard to implement in a language with a
| traditional compile/run phase distinction like Rust: the
| _full_ feature-set of dependent types is pretty much only
| available at compile time. (Which is why dependent-typed
| languages tend to add "program extraction" features, which
| reintroduce that phase separation in a rather ad-hoc way.)
| giraffe_lady wrote:
| I loved reading that part! I've always found ocaml's (and now
| rescript's) first class modules flexible and powerful.
| They're definitely awkward but in that way where you're
| having to explicitly think about and declare relationships
| you'd happily ignore until they bite you.
|
| Recently I've seen a lot of programmers I respect consider
| them a weaker or failed alternative to typeclasses, basically
| a dead-end. And I had been reluctantly coming to the
| conclusion that I must be wrong in some way I'm not able to
| fully perceive yet. Seeing such a notable PL designer come
| out on their side makes me feel less like a fool.
| eru wrote:
| Having used both OCaml and Haskell in production, I can say
| that the typeclass way of doing things is massively more
| convenient.
|
| But I guess enough syntactic sugar might make things
| palatable.
| skellington wrote:
| Committees tend to design less beautiful things. Look at modern
| c++ too. What a mess. Everyone in the committee has to jam in
| their favorite feature, much of which is sugar.
| eru wrote:
| Haskell is designed by committee, and it's a fairly alright
| language.
| tinco wrote:
| It suffers from the same problem C++ does though, it's got
| a broken "prelude" and the committee just doesn't have the
| courage to ever fix it. Weirdly a long time ago the opinion
| was that since the language was academic they weren't
| hindered by the demands of stability that corporate users
| might so they could do epic things like the switch to
| monadic I/O.
|
| I feel if the Haskell committee could simply pick one of
| the alternative preludes, and gather some packages into a
| new standard library, and announce it all as a Haskell 2.0
| or Haskell prime or whatever it could really reinvigorate
| the community. But what committee would be motivated to do
| such a thing, bearing the responsibility if it went wrong?
| marcosdumay wrote:
| They _are_ fixing it... it 's just happening at committee
| speeds.
|
| I would really like to be able to specify what prelude I
| want at the .cabal file. AFAIK, that's one change that
| wouldn't break anything.
|
| (Anyway, Haskell is one case of a language asking for a
| fork. Maybe it takes the form of Idris or some other
| derived language having some sudden growth instead, but
| you are right, the committee currently isn't as fast as
| the community.)
| asplake wrote:
| Modulo which prelude you're supposed use. That was enough
| to put this newcomer off
| nsajko wrote:
| Modern C++ is actually going relatively OK? You may be
| thinking of the old, pre-C++11 C++?
| chippiewill wrote:
| Modern C++ _is_ actually a mess to be fair. Overall it's
| still an improvement on pre-c++11, but when looking at it
| end-to-end it feels awfully disjointed.
| Sankozi wrote:
| If you have a mess and add something to it, you still have
| a mess, probably a bigger one. To clean the mess you need
| to remove something (which is really hard in a programming
| language) not to add something.
|
| Selecting and enforcing usage of a subset of C++ is a way
| to deal with that mess which companies frequently use.
| nsajko wrote:
| Everything is a mess. The universe is a mess. That
| doesn't make perfectionism a good thing.
|
| Besides, the C++ committee does, in fact, remove stuff
| from the language to clean the mess, just not without due
| care.
| rcxdude wrote:
| They do so once in a blue moon, and only for ideas so
| spectacularly bad that either they were never implemented
| or never used by anyone. Those aren't the main problem
| with C++ mess, the issue is the many small but important
| details which are deeply embedded into the language since
| it started as a super-set of C.
| zozbot234 wrote:
| > To clean the mess you need to remove something
|
| Sure, but that's why Carbon and Cppfront are a thing now.
| These two actively remove stuff from C++.
|
| We'll probably see this happen in the Rust community as
| well once the newer Crab language becomes established.
| Then we'll have to rewrite everything in Crab, but it
| will probably be a lot simpler than the Rust rewrite.
| [deleted]
| flohofwoe wrote:
| C++11 is exactly where the mess started ;) C++ releases got
| more frequent since then, but at the same time, more half-
| baked features slip in that haven't been battle tested in
| the real world before, or need refinement over the next
| decade worth of releases (if they can be fixed at all).
| rcxdude wrote:
| I don't think that modern C++ is any less messy than old
| C++. If anything it's more so, as even more difficult
| conscessions to backwards compatibility emerge. That's not
| the say the new functionality isn't useful, but I don't
| think it cleans anything up. Modern C++ is approaching an
| order of magnitude more complexity than pre-C++11 (which is
| not surprising if you are adding features but not removing
| or changing old functionality), and I don't think it's a
| sustainable increase.
| crispinb wrote:
| > Turns out that the person who originally created the language
| doesn't like them either.
|
| But it also turns out the very same isn't as neurotically
| attached to his 'likes' as most of us are:
|
| > The Rust I Wanted probably had no future, or at least not one
| anywhere near as good as The Rust We Got. The fact that there
| was any path that achieved the level of success the language
| has seen so far is frankly miraculous. Don't jinx it by
| imagining I would have done any better!
|
| He understands that his likes are just contingent facts about a
| single mammal, not truths. This is a hard-won understanding.
| Most people never reach it.
| rtpg wrote:
| I am very glad to not have Rust end up along that path (I am
| sympathetic to the threading idea he mentioned though).
|
| Rust as an alternative to C++ does, for me, involve all of the
| weird magical nonsense that you kind of need to get any of this
| working. The extreme use of generics to build out DSLs to get
| things working. General libraries being very hard to write, but
| still possible, to get alright ergonomics for usage itself. And
| yeah... the zero-cost abstraction thing.
|
| I think Graydon-Rust would have also been very interesting, but
| it sounds unappealing to me, person who wants "C++ but nicer".
|
| But to his point... saying "you could have much faster compile
| times" is very tempting! Just, especially when it's messing
| around with Rust "for fun", the ergonomics sound pretty unfun.
| xiphias2 wrote:
| Some Rust developer said that Rust was originally what Go has
| become.
|
| So if you want to try something like that, just look at Go :)
| hajile wrote:
| Go is actually a competitor with StandardML and loses badly
| in every way except having Google's deep pockets to carry
| it.
|
| StandardML uses a far superior Hindley-Milner type system.
| It has pattern matching. It has Option types for good error
| handling. It doesn't have bad features (for GC'd languages)
| like direct pointers and slices. It has immutability by
| default. CML even offers a better take on channels too.
| Modules keep interfaces more standardized (superior for big
| projects IMO). And to top it all off, SML is easier to
| learn than Go. It's also as fast as Go (despite the
| compilers being a side project).
|
| Go's only advantage is more extensive libraries, but that
| would be fixable in SML with just a fraction of the money
| Google spent on Go.
| bfrog wrote:
| After reading this I'm really quite happy Graydon created Rust,
| but then conceded the path it has taken.
|
| It really is an incredibly language and ecosystem, in large part,
| because of its performance potential.
|
| To be clear, the only real options in this space were arguably C,
| C++, and maybe in some circles D in my mind. C++ and C by far had
| the mind share.
|
| Had Rust gone the way Graydon wanted I don't think Rust would be
| so interesting in the OS and Embedded space. This is a space that
| it turns out is really ripe for change.
|
| Embedded application are growing more connected, and more complex
| all the time. Security is a serious concern perhaps followed by
| or proceeded by performance depending on who you ask. Rust checks
| so many boxes off in this space its really hard to argue that it
| isn't a better solution.
|
| Would you rather write a little embedded http server on an IoT
| device in C, C++, or Rust? What about an embedded networking
| stack? What about a mesh network stack? I know the answer I'd
| have every time for this myself.
| fallat wrote:
| Zig
| demarq wrote:
| beautiful ending!
| avgcorrection wrote:
| I haven't cared about what Hoare thinks of Rust since 2015.
|
| 1. He hasn't been involved in the language for a long time
|
| 2. His vision for the language was completely different compared
| to how the remaining developers ended up designing it. So if I
| ended up liking Rust under his "BDFL"ing then it would be for
| completely different reasons compared to why I like (and dislike)
| Rust today
| avgcorrection wrote:
| Hmm, I had only read a few paragraphs when I wrote that. Now
| I've read through the whole thing and it seems that Hoare
| agrees with me. How nice.
| svieira wrote:
| > Not 100% clear about actors -- I was weirdly focused on that
| model that in practice has many issues
|
| Does anyone have any insights on the particular issues with
| actor-based concurrency vs. "direct parallelism like threads or
| locks" that he might be thinking about here?
| codedokode wrote:
| I don't understand why in Rust integers wrap around. It doesn't
| make sense and there are vulnerabilities caused by wrapping
| integers in C.
| sdfghswe wrote:
| What's the alternative? What would you expect happens?
| p0nce wrote:
| > First-class &
|
| Interestingly like Graydon suggests this and 'tis something D
| has, you can only have `ref` for function parameters. This is
| something the users sometimes complain about, but I guess it has
| positives.
| FeepingCreature wrote:
| Yeah a lot of these features sound like he wants "D with
| lifetimes".
| LeanderK wrote:
| Can someone comment on "Library-defined containers, iteration and
| smart pointers"? I have no rust experience so far. Magic compiler
| support for such primitives is something I usually very much,
| really strongly dislike, it was always my experience that it is
| the wrong point of abstraction because it just reduces the design
| state so much. But then you need good support for inlining the
| relevant parts of the language, which I think is very often very
| poorly done. In fact, I have so far never seen a solution I like.
|
| What is the rust experience wrt to inlining? Can every expression
| be inlined or only selected ones? How can you know what got
| inlined in some expression? Do you have to manually annotate
| every single function call you have to inline or is there a more
| general command?
| tijsvd wrote:
| Everything that's visible to the compiler is subject to
| automatic inlining. That is all code in the current crate
| (compilation unit), all concrete instantiations of generics
| (regardless where defined), and all functions marked inline
| (regardless which crate).
|
| Stdlib containers are all in the generics category.
| LeanderK wrote:
| > all functions marked inline (regardless which crate).
|
| Is this a problem is rust? Is too much/too little marked
| inline? What if you really need to inline some function from
| some library that was not marked inline by the author?
| JonChesterfield wrote:
| If containers, control flow and so forth are expressible as
| library code you get a simpler compiler and stuff determined
| users can reasonably debug, modify, replace. It means you have
| to improve the core language enough to implement them and/or
| have magic compiler intrinsics which are only intended for use
| by that library.
|
| If you implement these things in the compiler (open code means
| emit the implementation inline as you go, can also emit calls
| to the compiler runtime which is roughly similar to library
| code that the compiler ships and knows lots about) then users
| need to hack the compiler to change them.
|
| However, if the structures are in the compiler, and you've done
| things like encode them directly in the AST, the compiler has a
| better chance of emitting useful diagnostics for them and of
| optimising them at the semantic level of the container.
|
| C++ goes with library code supported by compiler intrinsics,
| and a common developer experience is compilation errors
| referring to iterators some distance into the library code. It
| also can't sanely do things like call reserve on a vector
| outside of a loop, because by the time it's ready to optimise
| things it's holding raw pointers with mangled names, not a
| hashmap instance.
|
| Conventional wisdom is to put containers in the stdlib. D has
| some support in the compiler. I'm starting to think this is one
| where conventional wisdom has got it wrong.
| moonchrome wrote:
| As someone casually following Rust and haven't touched it or C++
| in probably 5+ years (but keeping tabs in hope of coming back to
| that world), I feel like a lot of decisions he mentions are what
| made me optimistic about Rust adoption/positioning itself as a
| modern C++ replacement. So I kind of agree with his conclusion -
| his version of Rust would be much less interesting to me, and I
| think a lot of it's current users. eg. I only started playing
| with Rust once they removed green threads. Zero cost abstractions
| are a major selling point.
| rob74 wrote:
| I think this quote also applies more generally:
|
| > _It 's easier to work with than C++, but that's fairly faint
| praise._
|
| So, while Rust positions itself as a C++ alternative with all
| the complexities that C++ developers love, it may become less
| attractive for other use cases. For instance, I find it highly
| questionable to develop a web app in Rust...
| [deleted]
| smabie wrote:
| There's tons of existing languages that are mostly okay that
| you can develop a webapp in.
|
| Rust as a C++ replacement serves a real unmet need in the
| marketplace.
| devjab wrote:
| We build a proof-of-concept backend replacement for what is
| essentially "SharePoint being used as a DB with a frontend by
| people who don't know how to use SharePoint" and was a nice
| experience. We also build it in a few other languages, C#,
| Go, Python and TypeScript and Rust was probably the best
| experience of them all. We ended up going with C# because we
| needed Odata, and at the time we hadn't yet run into the many
| "joys" of working with Odata, ASP and Entity Framework and
| how their model builders, really, really, really, won't play
| together nicely.
|
| Knowing what we know now, we should've gone with TypeScript
| and just written our own Odata filter on top of it, but live
| and learn.
|
| If I was in a position where I could pick and chose
| languages, and not worry about how not having TypeScript in
| most things will mean our best front-end developer can never
| go on vacation because he's sort of our only front-end
| developer, I wouldn't mind using Rust for web-backends.
|
| Rust GUI is obviously not a great experience. At least not
| yet. But it's not that bad either. I think it's mostly the
| case of how JavaScript is just so good at it and seeing such
| a fast pace of improvements because the entire world uses it
| for most GUIs these days, that it's just hard for anything
| else to compete. I mean, look at stuff like Flutter or
| Blazor, they are backed by Microsoft and Google and they're
| vastly inferior choices for most use cases compared to simply
| building things in React, ReactNative or even electron, and
| that's not because I have some wild love for JavaScript, it's
| because it's seeing rapid improvements they dwarf it's
| competition simply by being used by a lot of people.
|
| I wish Rust would have someone like Facebook pick it up and
| build a frontend framework for it, but I think that is just
| too unlikely for you to bet on, and you certainly wouldn't
| want to do it yourself, even as open source because then that
| would probably be your entire job.
|
| On the flip-side, the packages that handle basic back-end web
| stuff for Enterprise use are rock solid in Rust. Which is
| impressive, at least to me, considering it's young age. I
| have no idea why, but maybe some serious players are
| contributing to it because they use it themselves. There
| isn't a "Django" or Ruby+Rails for Rust, but if what you're
| building is a lot of smaller APIs with various transport
| methods and data access in an federated authentication
| scenario then Rust is surprisingly mature for the web. It's
| primary disadvantage being that your TypeScript and Rust
| developers won't be able to cover for each other (which is
| why we didn't poc with Java).
| internetter wrote:
| > I wish Rust would have someone like Facebook pick it up
| and build a frontend framework for it
|
| We have yew, and my personal favourite: leptos
| (https://leptos.dev/)
| lumb63 wrote:
| As someone who recently started their first web app in
| Rust and is using Yew for the front-end, I wish they
| included benchmarks against other Rust frameworks. I also
| wish I had more experience and had looked harder; Yew is
| very verbose in my experience and Leptos looks a little
| more sane.
| [deleted]
| tick_tock_tick wrote:
| > it may become less attractive for other use cases. For
| instance, I find it highly questionable to develop a web app
| in Rust...
|
| I mean who'd want to and why bother trying to support that
| use case? Basically none of Rust's value proposition exists
| for a web app while nearly every single one of the downsides
| do.
| inferiorhuman wrote:
| Hard disagree. Rust brings a lot to the table for a web
| app. Compiled vs interpreted makes it easier to catch
| (syntax) errors beforehand. You can even go as far as
| compiled templates. Mapping JSON (or whatever) to strongly
| typed objects is _great_. Dependency management is best in
| class. I rather like diesel.rs (although it is very much an
| acquired taste) especially if you treat it like a safe
| query builder rather than an ORM.
|
| The single biggest problem I've run into is that there's no
| real good story for a web app framework (especially since
| everyone's gone crazy over async). Rocket is perhaps a bit
| too magical for rust folks and it's been abandoned, but I
| rather liked it. I've warmed up to axum, but all this async
| stuff still rubs me the wrong way.
|
| And, yes, compile times still suck.
| EdwardDiego wrote:
| > Dependency management is best in class
|
| Do crates have namespaces that ownership is verified for?
| geodel wrote:
| Crate namespaces is best-in-class++ feature and currently
| not available.
| EdwardDiego wrote:
| Yeah, I ask because the JVM world has had that since...
| whenever Maven's central repository came about [0][1][2],
| I guess, and it ensures that there's no debate about
| namesquatting [3], nor typosquatting attack vectors [4].
|
| I am constantly baffled that NPM, PyPi, crates.io etc.
| didn't copy this idea for those last two reasons. In my
| mind, it's not quite best in class without it.
|
| [0]: https://central.sonatype.org/publish/requirements/co
| ordinate...
|
| [1]: https://central.sonatype.org/faq/how-to-set-txt-
| record/
|
| [2]: https://central.sonatype.org/publish/requirements/co
| ordinate...
|
| [3]: https://internals.rust-lang.org/t/pre-rfc-formal-
| squatting-p...
|
| [4]: https://blog.sonatype.com/this-week-in-malware-
| may-13th-edit...
| Gigachad wrote:
| Arguably TypeScript is pretty close to as safe while
| being less work.
| inferiorhuman wrote:
| Perhaps. But TS is still Javascript under the hood which
| means implementation details leak and they smell awful.
| If they didn't it'd come down to a preference for
| compiled vs interpreted.
|
| The number one syntactical beef I've is operators. e.g.
| == vs ===, ""+number.
| vsnf wrote:
| Although all of those things are true, I think the main
| point being made is why choose _Rust_ for that, instead
| of Go, .NET, etc, any of which offer everything you 've
| outlined as being worth having, while having fewer of the
| downsides of Rust.
| inferiorhuman wrote:
| any of which offer everything you've outlined
|
| They don't, that's the "problem". Off the top of my head
| dependency management in Go is not best in class to put
| it charitably. .NET will tie you more closely to Windows.
| Sure, mono is a thing but you'll have more packages to
| choose from and fewer compatibility issues running .NET
| on Windows.
| foobarbaz33 wrote:
| Mono? 2000 and late called and want their .NET
| implementation back. Now it's all about the .NET formerly
| known as "Core".
| Paradigma11 wrote:
| Web development for .NET is multiplatform for quite some
| years now. https://en.wikipedia.org/wiki/.NET
|
| Mono is mostly only relevant if you need platform
| specific capabilities that wont be covered in .NET.
| Merad wrote:
| Your knowledge of the .Net ecosystem is a bit out of
| date. It's had first class support for mac and linux for
| 7-8 years now.
| scns wrote:
| First class support on Linux and Mac with caveats. Zero
| snark intended.
| LinXitoW wrote:
| To me, one thing missing from most popular web languages
| is the single best part of Rust: A well-defined handling
| of the non-happy path.
|
| I'm talking about things like exhaustive "switches",
| built-in, ENFORCED handling of "missing" values (null,
| undefined), and finally useful error handling.
|
| I actually don't need any of the baremetal features of
| Rust. It's just that most of it's "zero-cost"
| abstractions are still far superior to those of other
| languages.
| kaba0 wrote:
| Haskell, Scala, F#, OCaml.
|
| (Some dislike it, but Java's checked exceptions are exact
| homologous of result types)
| marcosdumay wrote:
| The problem with Java exceptions is that they have very
| poor generic support.
|
| Outside of that, yeah they just differ in very low-level
| aspects that most developers don't even know about.
| pjc50 wrote:
| > Compiled vs interpreted makes it easier to catch
| (syntax) errors beforehand. ... Mapping JSON (or
| whatever) to strongly typed objects is great
|
| _cough_ C#. You also get LINQ. And a fairly heavy amount
| of web frameworks in ASP.NET / Razor.
| kaba0 wrote:
| Or Java, F#, Scala, Kotlin, Haskell. If we were to
| randomly pick a language, chances are it could be a good
| fit for these -- Rust became as well-known as is because
| it was made for a different niche, where there were no
| competition.
| moi2388 wrote:
| Haskell is a beautiful language, but I find their web
| frameworks rather cumbersome to work with personally.
| EdwardDiego wrote:
| Hell, even Python can map JSON to a strongly typed
| object. I am a firm fan of Pydantic and its competitors
| when dealing with JSON blobs. No-one deserves a KeyError
| in prod code.
| Vespasian wrote:
| I am doing that for a just for fun side project and it was
| an interesting experience.
|
| The pieces and features i wanted exist but tying them
| together was not always straight forward.
|
| From the top of my head I used:
|
| - actix-web
|
| - tracing (logging)
|
| - sea-orm
|
| - config from yaml files and a custom implementation to
| allow profiles.
|
| - utoipa (for openapi and swagger ui)
|
| - thiserror
|
| In a professional context I would not have used Rust for
| this project, but it was quite fun to explore it's
| possibilities and rich crate ecosystem.
| FpUser wrote:
| >"I mean who'd want to and why bother trying to support
| that use case"
|
| I write my web apps in C++. They tend to be little bit more
| than just query database / update database. They're exposed
| as JSON based RPC and can be accessed by JS front end from
| browser or third party systems we interact with. The
| performance is stellar and the code size is not much
| different comparatively to using PHP / Ruby / Python /
| your_pet_goes_here.
| throwaway2037 wrote:
| Nice. Do you have some open source samples? I would like
| to see your style!
| FpUser wrote:
| No. I do not work on opensource projects unfortunately.
| moonchrome wrote:
| >So, while Rust positions itself as a C++ alternative with
| all the complexities that C++ developers love, it may become
| less attractive for other use cases. For instance, I find it
| highly questionable to develop a web app in Rust...
|
| But we already have plenty of options for this use case
| (.NET, JVM, Go, Python, Ruby, node.js, PHP, Erlang, etc.
| etc.). Very mature ecosystems that solve a lot of different
| edge cases.
|
| There are very few C++ alternatives worth mentioning, none as
| mature as Rust in terms of adoption/tooling.
|
| If you need to write a webapp that would require C/C++ kind
| of memory handling/performance then Rust would be the ideal
| candidate I think.
|
| I don't know what kind of tradeoff matrix makes you use the
| Rust graydon describes over the existing options.
|
| For example my main issue with D (but TBH that's something
| I've given up on actively tracking 10 years ago probably) is
| that by including GC in the runtime/stdlib - it basically
| painted itself as a poorly supported competitor to C# rather
| than a C++ replacement.
| sph wrote:
| It makes me smile that my top-level comment says I would have
| preferred the ML-version of Rust, while you say you prefer the
| zero-cost-abstraction version of Rust that's more C++-like.
|
| Indeed in software engineering there is no silver bullet nor a
| perfect language for everybody :-)
| kaba0 wrote:
| ML-version of Rust would not have been a uniquely interesting
| language. It targeting the C++-niche is what made it into
| quite the big name it has become.
| unrealhoang wrote:
| There's already a ML-version of Rust, it's Ocaml
| hajile wrote:
| I'm pretty sure that the ML version of Rust is actually
| ATS.
|
| https://www.cs.bu.edu/~hwxi/atslangweb/
| kzrdude wrote:
| I was at the sidelines when Rust 1.0 was being made and I think
| it got into an llvm induced feedback loop. Slowly turning into
| C or C++ with other features but the same type, object and
| memory model.
|
| Part of the reason was Rust's desire to show itself as a direct
| competitor w.r.t performance, I think.
| Ygg2 wrote:
| Performance, but with sanity.
|
| Say you use a vector in C++. You push one element and pop
| two. In Rust that's a None. In C++ the answer is UB (in my
| case 43).
| pjmlp wrote:
| In C++ it is an error, if one bothers to enable bounds
| checking.
|
| https://godbolt.org/z/vYcMhE9h7
|
| > Error: attempt to access an element in an empty
| container.
| Ygg2 wrote:
| It's not an error by default and more importantly it's
| not an error under right circumstances (e.g. lets say
| number of pops and inserts is supplied via arguments).
| pjmlp wrote:
| Yes defaults matter, it doesn't change the fact that
| protective gear is available, and like seatbelts, helmets
| and motorrad full body armour, it is up for security
| conscious people to make do of what is made available to
| them.
|
| As for your dynamic arguments request, have fun,
|
| https://godbolt.org/z/YYKrnh4Y9
| Ygg2 wrote:
| It's not just that defaults matter, the problem here is
| why have footgun as the default?
|
| In a runtime example I can run it with tests and it would
| behave fine if both values are same or first arg is
| bigger, in Rust's case it would behave valid for ANY
| combination of arguments.
| dmm wrote:
| Someone much more insightful than me pointed out that
| most of the safety advantages of Rust are really a
| cultural phenomenon, rather than a strictly technical
| one. You could write unsafe unsafe Rust that derefs
| invalid pointers all day but when building systems and
| libraries with Rust, people value safety and Rust enables
| that as a priority.
| pjmlp wrote:
| Yes, that is kind of true.
|
| It is also what attracted me into C++ coming from Turbo
| Pascal and Turbo Basic, back in the early 1990's.
|
| Although C++ culture could be much better towards safety,
| it is definitely better than whatever WG14 is doing, or C
| has brought into the picture for the last 50 years.
|
| Also anyone that just copy pastes C like code into C++,
| is the kind of developer that will be using unsafe{} all
| over the place, on the languages that have them.
| imtringued wrote:
| Telling people to stop using unsafe is much easier than
| telling people to not have undefined behaviour.
|
| C developers like telling themselves that only people
| with bounded rationality make security critical mistakes.
| All the skilled C developers have ascended beyond the
| mortal realm and would never let themselves be chained up
| with crutches for the weak like affine types or
| overflow/bounds checking.
| worik wrote:
| > a cultural phenomenon, rather than a strictly technical
| one. You could write unsafe unsafe Rust that derefs
| invalid pointers all day
|
| Yes you could. But I do not think " cultural" is the
| right term for not putting all your code in an `unsafe`
| block
| intelVISA wrote:
| The fatal mistake here is using the STL...
| tialaramex wrote:
| The safe thing Rust did here is affordable in Rust
| (Option<T> is the same size as T for many T including all
| references) so they could afford to do it, whereas it's
| expensive in C++. Could it have been made cheaper in C++?
| Sure, but safety wasn't their priority so who cares?
|
| That prioritisation applies to the whole ISO language,
| not only to the standard library.
| pjmlp wrote:
| Talking about C?
|
| https://godbolt.org/z/vYcMhE9h7
| tialaramex wrote:
| You turned on a feature which helps diagnose this type of
| mistake at runtime, and it helped you by diagnosing the
| mistake at runtime. What does that prove?
|
| What I'm talking about is that Rust's Option is very
| cheap in all the cases where it can be very cheap, which
| makes this whole design feature more affordable. C++
| eventually grew std::optional which is not powerful
| enough for this work and yet is also bigger and slower.
| They _could_ have done better, but safety wasn 't a
| priority.
| pjmlp wrote:
| The fatal mistake is not enabling bounds checking, which
| most STL implementations support.
| intelVISA wrote:
| -Wabsolutely-everything
|
| I haven't written the Language of Kings in a while: does
| GCC -Wall truly enable all warnings yet?
| staunton wrote:
| Obviously not. It would be an instant scandal if they
| tried that.
| FpUser wrote:
| And what should be defined behavior? There are quite a few
| choices that depend on particular situations. Feature
| designers have no knowledge about what would you want so
| they left it up to application programmer to check the
| situation upfront and act accordingly to their wishes. If
| you want same behavior across the whole application you can
| always write generic function doing just that.
| Ygg2 wrote:
| Return either value or an EMPTY placeholder. Look Java
| did it. It returns a nullable value or Optional.
| FpUser wrote:
| Then I have to check the result anyways. Same thing
| [deleted]
| Ygg2 wrote:
| No. In Rust check is there by default, with optional
| unchecked access. In C++ the safety is off by default and
| you have to remember to check.
|
| It's like Yaml parsers, they had "load_yaml" which is
| unsafe and "safe_load_yaml" which is the secure option.
| Imagine no surprise when Rubyists went for shorter safe
| looking method and got their servers pwned.
| FpUser wrote:
| I think you are wrong. I looked and in Rust doc it says
| that: "Removes the last element from a vector and returns
| it, or None if it is empty". You better be doing check
| for "None".
| Ygg2 wrote:
| Problem is, you call pop on empty vector in C++, you get
| nasal demons. Not a case in Rust.
|
| I'm not a C++ expert but here is my understanding. Vector
| is essentially a tuple of (dynamic_array_address: ptr,
| size: size_t, capacity: size_t). To pop a value from
| vector you just decrements size. So what happens when you
| have empty vec? Your size is 0, and you're substracing 1,
| which causes undefined behavior.
|
| Correct way is to check BEFORE you pop_back().
|
| In Rust, any number of invocation of pop() will not
| result in undefined behavior. You can ignore the value,
| or you can check it, but it doesn't expose you to UB just
| for slightly misusing a vector.
| LegionMammal978 wrote:
| The check is still there, that's not under dispute. GP is
| saying that it's still not the "same thing" as C++, since
| Rust will refuse to compile the code unless you either
| perform the check, or actively opt into unchecked access.
| In contrast, C++ will happily compile the code if you
| unintentionally neglect to perform the check.
| FpUser wrote:
| Are you saying that Rust will refuse to compile code that
| does not explicitly check option return result for empty?
| __jem wrote:
| Yes, you cannot use the result without matching on the
| result/option type.
| Ygg2 wrote:
| >Are you saying that Rust will refuse to compile code
| that does not explicitly
|
| Depends what you mean by explicit.
|
| Look at it like this: pub fn main() {
| let mut vec : Vec<i32> = vec![]; let x =
| vec.pop(); println!("{:?}",x); // prints: None
| }
|
| In this case you see the value is missing.
| vec.pop().expect("I want a value")
|
| will panic with "I want a value" because value is empty.
| And most rigorous way to deal with it is:
| if let Some(x) = vec.pop() {
| println!("{:?}",x); } else {
| println!("EMPTY"); // prints: Empty }
|
| will either print value or EMPTY depending on what the
| vector contains.
|
| Types like Result will issue warning that you didn't
| handle the cases.
| dureuill wrote:
| To be precise, Rust will refuse to compile code that
| accesses the value without checking that it is not empty.
|
| Rust does that with the combination of "sum types" (aka
| "enums with data") and pattern matching. An `Option<T>`
| is either `Some(T)` or `None`. Matching on an option with
| the pattern `Some(value)` creates a new syntactic scope
| where the value is accessible. This scope is only entered
| if the `Option` is actually non-empty.
|
| All ways of getting the value from the option are
| ultimately shorthand for a match in that way. For
| example, option.unwrap() will either get the value if it
| there, or panic. Option.unwrap_or(x) will either get the
| value of it there, or use x instead.
|
| In practice this is x100 less error prone than C++.
| Source: was burned by vector.front() on empty vectors
| more than once. In C++, UB. In rust, usually the "empty
| case" is considered when first writing the code, or at
| least caught during review (unwraps tend to be very
| visible in reviews)
| mughinn wrote:
| You should always check
|
| The argument is that Rust forces you and in C++ you can
| forget/the compiler can do what it wants
| sheepscreek wrote:
| I'll get straight to the point I want to make: Rust is suffering
| from an identity crisis. Much like Javascript.
|
| Realizing this, I thoroughly feel the need for a "Rust, the good
| parts" doctrine.
|
| A good portion of use-cases could be successfully implemented
| with a small subset of language. The small subset doesn't need to
| be any more complicated than Go. And in doing so, we'd be
| reducing the entry barrier for masses and encouraging wider
| adoption.
|
| Edited: For clarity
| rurban wrote:
| Interesting. So it looks like once he let the C++ folks in, they
| started to damage it. As they did with their own design decisions
| before.
| sapiogram wrote:
| > So it looks like once he let the C++ folks in, they started
| to damage it.
|
| At least in terms of his personal preferences. I don't think
| it's true in terms of market share, given the massive success
| of Rust.
| superkuh wrote:
| Funny how every time I click a link to a dreamwidth.org hosted
| blog the,
|
| >"Hello, you've been (semi-randomly) selected to take a CAPTCHA
| to validate your requests. Please complete it below and hit the
| button!"
|
| ...pops up and the button doesn't actually work. Truly one of the
| worst blog hosts out there if you actually want everyone to be
| able to read what you write.
| parasense wrote:
| He's already created several programming languages, and there is
| still time to create his ideal.
| cryptonector wrote:
| > Environment capture. I often say (provocatively) that "I hate
| lambda", but lambda is actually (at least) two separate language
| features: one is a notation for anonymous function literals;
| another is equipping those anonymous function literals with
| environment capture. I don't really care either way about having
| such a literal syntax, but I really do dislike equipping it with
| environment capture and think it's a mistake, especially in a
| systems language with observable mutation. Early Rust had
| something called "bind expressions" which I copied from Sather
| and which I think are better (clunkier but better). Rust gained
| lambda-with-environment-capture in a single package which I
| didn't object to strongly enough, as part of trying to make
| interior iteration work on LLVM-with-no-coroutines, and this
| motivation was later removed when we moved to exterior iteration.
| But "if I were BDFL" I would probably roll back the environment
| capture part (it's easier to tolerate for non-escaping closures
| which many are, eg. in non-escaping coroutines / stack iterator
| bodies, but .. eh .. complex topic).
|
| TFA loses me here. I rather like the Fn/FnMut/FnOnce business,
| though yeah, closures of dynamic extent are very limited unless
| you Box them to make them of indefinite extent... and so the
| whole language lacks that character that functional languages
| with GCs have, but it's still functional, just functional with a
| straight-jacket.
|
| Earlier in TFA there's a mention of exterior iteration as in
| generators, and I want to point out that while generators are
| very nice, they are not a substitute for closures. Icon, for
| example, had iterators and first-class co-routines ("co-
| expressions"), but no closures, and so where one needed closures
| one had to use co-routines (costly!). It's true that with
| generators one needs closures less than without generators, but
| still, closures are very important.
|
| > Traits. I generally don't like traits / typeclasses. To my eyes
| they're too type-directed, too global, too much like programming
| and debugging a distributed set of invisible inference rules, and
| produce too much coupling between libraries in terms of what is
| or isn't allowed to maintain coherence. I wanted (and got part
| way into building) a first class module system in the ML
| tradition. Many team members objected because these systems are
| more verbose, often painfully so, and I lost the argument. But I
| still don't like the result, and I would probably have backed the
| experiment out "if I'd been BDFL".
|
| This loses me too.
|
| It's great then that Rust didn't have a BDFL! :)
|
| > Underpowered existentials. The dyn Trait mechanism in Rust
| allows runtime and heterogeneous polymorphism, a.k.a.
| Existentials. These types are useful for many reasons: both
| solving heterogeneous representation cases and also selectively
| backing-off from monomorphization or inlining (when you have
| those) in order to favour code size / compile time or allow
| dynamic linking or runtime extension. Early Rust tried to use
| these extensively (see also "first-class modules") and actually
| had an intermediate-rigidity type called an obj that was always a
| sort of Cecil-like runtime-extensible existential glued to a
| self-type record that allowed method-by-method overriding at
| runtime (almost like a prototype-OO system). Today's Rust
| strongly discourages the use of any such dynamic dispatch, a
| feedback loop arising from both technical limitations placed on
| them and a library ecosystem that's taken that as a sign never to
| use them.
|
| This, on the other hand, is a brilliant observation and I agree
| with it as with much else in TFA.
|
| > Tail calls. I actually wanted them! I think they're great. And
| I got argued into not having them because the project in general
| got argued into the position of "compete to win with C++ on
| performance" and so I wound up writing a sad post rejecting them
| which is one of the saddest things ever written on the subject.
| It remains true with Rust's priorities today, I doubt it'll ever
| be possible across crates (maybe maybe within), but as with stack
| iterators IMO they're a great primitive to have in a language, in
| this case for writing simple and composable state machines, and
| "if I were BDFL" I probably would have pointed the language in a
| direction that kept them. Early Rust had them, LLVM mostly made
| us drop them, and C++ performance obsession kept them consigned
| to WONTFIX bug status.
|
| Oh dear. TCO is essential, IMO. I understand that it may not be
| possible in cross-crate cases, but still, TCO is very important.
| PhilipRoman wrote:
| Haven't followed Rust too much, but I'm always surprised when I
| hear that Rust is too difficult or not ergonomic. As I understand
| it is meant to be a systems level language; something you'd use
| to write kernels, TCP stacks, browsers and ssh daemons.
|
| Anyone writing these things today in C or C++ already understands
| object lifetimes and Rust just adds a static checker for them.
|
| In such projects churning out lines of code is not the
| bottleneck, ease of development should not be prioritized over
| long term maintainability.
|
| Why on earth would you try to rewrite python CRUD apps in Rust?
| LinXitoW wrote:
| > Why on earth would you try to rewrite python CRUD apps in
| Rust?
|
| Because Rust has a lot of incredibly helpful features that make
| bigger systems far less of a pain to maintain. I work in
| Java/Kotlin, and my entirely gut-based estimate is that 66% of
| problems wouldn't happen in Rust.
|
| My big favorites are: - Sane, well-defined, enforced, opt-out
| error handling - Sane, well-defined, enforced, opt-out handling
| of "missing" values - Exhaustive switching - WITH usable ADTs
| (enums) for encoding valid state
|
| None of the web languages I know have all of that.
| marcosdumay wrote:
| So... Haskell, OCaml, F#, Elm?
| kaba0 wrote:
| Scala has all of that and predates Rust by far.
|
| But even Java is pretty much there: checked exceptions are
| available (and imo superior), but Optional is also an..
| option. Kotlin/scala can handle nulls, but java can also
| statically analyse every usage with annotations.
|
| switch expressions are exhaustive in java over enums _and_
| sealed ADTs. (Rust has a misnomer, their enums are ADTs, java
| has both real enums and ADTs now).
| pitaj wrote:
| Rust enums cover both ADTs and enums.
| moonchrome wrote:
| >Anyone writing these things today in C or C++ already
| understands object lifetimes and Rust just adds a static
| checker for them.
|
| As someone who only used Rust casually - understanding object
| lifetimes and knowing how to encode this in Rust type system is
| not the same thing. Not to mention that Rust can't statically
| prove some things that are valid (eg. cyclic references).
| twic wrote:
| > Anyone writing these things today in C or C++ already
| understands object lifetimes and Rust just adds a static
| checker for them.
|
| I've certainly seen seasoned C++ programmers saying this. Of
| course, a few of them of say they understand object lifetimes
| so well, they they don't need the static checker!
|
| > Why on earth would you try to rewrite python CRUD apps in
| Rust?
|
| This is one of the great mysteries of our times. I do think the
| enthusiasm for using Rust for web apps and such (a) is
| misplaced and (b) has been a drag on Rust developing into a
| better C++ replacement.
| bluGill wrote:
| > Of course, a few of them of say they understand object
| lifetimes so well, they they don't need the static checker!
|
| There is a difference between understanding lifetimes and
| being able to keep track of them. I have a lot of objects in
| my more than 10 million lines of code. Most of them have
| simple lifetimes that are easy to track, but a few for
| reasons (which may or may not be valid - often the reasons
| are it was built in C++98 and updating to modern lifetimes is
| hard when it is used all over) have complex lifetimes that
| are tedious to track. It isn't that I can't, it is that I get
| bored/make mistakes and the static analyzer wouldn't (Or
| course C++ can't be statically analyzed, but if it could the
| static analyzer wouldn't fail for the same reasons I fail)
| xiphias2 wrote:
| I think there's enough data that even the best human
| programmers and lifetime checkers aren't able to track
| lifetimes over a 10 million line code base.
|
| That's why we're stuck with lifetimes as function
| contracts.
| iMerNibor wrote:
| Being able to stop constantly keeping things in the back of
| your head and just trusting the compiler to complain if
| something is off was the biggest differentiator for me by
| far. Less footguns = more better
| bluGill wrote:
| When I switch to C++11 and unitue_ptr things got a lot
| better. There is still a lot of cruft from old code, but
| C++ is a lot better as of 12 years ago. I don't let
| people manage raw pointers without good reason (I
| wouldn't let someone use unsafe rust without good reason
| either)
| lukebitts wrote:
| I personally really like the type-system, for any kind of
| project really
| jimwhite42 wrote:
| On the "Async/await" wrt IO and FFI, I wonder what Graydon and
| others think about the Erlang BEAM or the GHC runtime
| implementation.
| olalonde wrote:
| > Explicit lifetimes
|
| I never understood that one either. There is always only one
| "solution" that will make your program compile, so why not just
| let the compiler figure it out?
| steveklabnik wrote:
| I wrote a blog post about this.
| https://steveklabnik.com/writing/rusts-golden-rule
|
| I did not mention lifetimes, but the principle is the exact
| same: the lifetime annotations are an API promise, and so
| inferring them means that a change in the body of the function
| would change the promise, breaking other code.
| worik wrote:
| > basically every language discovers the long way that financial
| math is special and, at great length, eventually adds a decimal
| type.
|
| And every case it is wrong
|
| Financial math is not hard when you know how
|
| Use integer types not horrific cludges like "decimal"
|
| Just because IBM did it does not make it right. It is wrong
| smasher164 wrote:
| I admire Graydon's humility on the subject--and sure enough, I
| disagree with some of his ideas in this post, and agree with
| other ones.
|
| - Explicit lifetimes are what make rust what it is. It would have
| surely failed had they not been introduced.
|
| - I disagree about having a first-class module system instead of
| traits. Coherence and implicit instance resolution are a core
| value of Rust. `Send / Sync` are key examples of that.
|
| - Green threads probably wouldn't been viable for rust, given the
| kind of programs it's targeting.
|
| - Pretty much everything else however, I agree with.
___________________________________________________________________
(page generated 2023-06-05 23:04 UTC)