Rust, an Anti-Sloppy Programming Language
arthurtw.github.io
arthurtw.github.io
I think that higher-kinded types will probably help a lot with this if they make it into the language, and it's likely that there's a way to get around the errors and get the full benefit from the algebraic data types and monadic functions that I just haven't found yet. The language is enough of a level-up from my C++ days that I'm sure I'll stick to it, even if those things aren't true.
I thought this quote from the article really nailed it regarding my recent experience:
"Expressiveness or elegance is not a goal of Rust. It’s certainly not bad in this regard, just not as wonderful as you may wish if you care it a lot."
While I could freely pass a file handle into multiple functions, I could not do the same with any standard implementatoins of mock read/write object due to the underlying Vector objects implementing the `drop` method, which disallowed re-use of the object (including simply reading the contents after coming out of the function).
That was simply the compiler preventing you from using memory after it was freed. It sounds like a case of not calling "clone" to copy the vector, instead of anything relating to nonlexical borrow scopes.
They want to make this better.
This is enough of a priority that I foresee it landing in the language sometime in 2015.
How would HKT help with this? It sounds like a nonlexical borrow scope issue.
I don't find nonlexical borrow scopes to be a big pain anymore now that I'm used to how borrowing works (and I write thousands of lines of Rust code a week), but they can be annoying when getting started. They're definitely something that can be added backwards compatibly post 1.0; the semantics are not as trivial as they may seem though (you have to do union and intersection of arbitrary regions of a control-flow graph, or loop-nesting tree).
> It becomes tempting to just punt on the error handling and call unwrap() on everything just to get it working for now, which leads to problems later on.
If unwrap() works for you, why not use the try! macro to make your error handling correct? If it's truly borrow check errors you're having, calling try! is treated exactly the same way as the compiler as unwrap() is—but with try!, you will handle errors correctly.
> If unwrap() works for you, why not use the try! macro [...]
I was under the impression that the whole `try!` situation would become nicer with HKT. It wouldn't solve the problem you've identified, but wouldn't it make the code nicer (in some peoples eyes at least)?
(Edit: formatting.)
Instead, what HKTs would actually be useful for is something else entirely: generic algorithms that need to (for example) work with different kinds of collections' iterators and can be specialized for them at runtime, without paying the price of allocating the iterators on the heap. For example, consider a graph algorithm that's parameterized over the type of graph. (Note that I've never actually needed to write one of these algorithms in a generic way myself, but I can see it being useful in some circumstances.)
What HKT is not there for is Haskell-like monadic "do" notation. That simply isn't idiomatic Rust, and I don't foresee it becoming so in the future. The try! macro is the way you do this kind of thing, and it's already shipping today without HKT as a type system feature.
Unwrap only works in a loose sense. Writing a parser generator, I want to have it give usable parse error messages. Sometimes this can be done easily with a try!, but many times I should be doing a bit more processing on the error in order to get things like line numbers, input samples, and expected alternatives in the mix.
> and I write thousands of lines of Rust code a week
This is what I think I need to be doing ;). I'm not convinced yet that what I'm trying to do can't be done elegantly in the language, it's just uncommonly hard for me to "get it." I've been writing code professionally for 14 years, and have used over 30 languages in various sized projects during that time, and Rust has given me the biggest skill-check of the bunch. I actually think that my experience with other languages that have similar abstractions is getting my way more than helping.
Regarding HKTs, I'm not 100% certain they would solve my problem, but I frequently find myself wishing I could work in that space. I definitely miss Haskell's do-notation, which I think would be a potential feature in the language with a true monad (I realize there's a macro for it currently, but I haven't played with that yet).
In my original comment I said that my problem was a feedback between algebraic data types and borrow checking, but I think on further reflection that it would be more accurate to say it's between closures and borrows. The and_then function of Option and Result allows me to use them as monads, but doing so means I make lots of one-shot closures. I think the interaction that keeps stalling me is actually there, frequently with the &mut self from the parent function being used inside the closure in a way that's not allowed, or with a non-copyable type being used in the closure. I can get around it with some matching, testing, and unwrapping, but it doesn't feel nearly as solid and clear as the and_then approach.
I think Rust is an awesome language so far, and I think it's "shortcomings" are likely really my own shortcomings, but I have to say that it's been full of surprises so far. The advanced features I keep expecting to have seem to be locked away behind memory management barriers. I'll definitely keep working with it, and I suspect that I'll never turn back to C++ if I have the choice, so it's a triumph in that regard. It's just so tantalizingly close to being the "one true language" that I'm constantly expecting things of it that probably aren't quite realistic.
Meanwhile, to continue learning Rust, I've done myself a favor. I just told myself: "Rust is worth writing but it isn't for pretty code. Sorry!".
The other, related, mental block I haven't been able to overcome yet: In Java/Python/Ruby/(even C++), the runtime "seems so heavy" I don't care when I "waste" allocations or memory. However, the fact that it says on Rust's home page "featuring zero cost abstractions" makes me feel guilty/incompetent every time I introduce a cost-full abstraction. Again a reason I stopped learning Rust a few times.
Sadly this feeling of inadequacy/guilt extends even to applications. Which is ridiculous since if your application isn't doing expensive things, you probably have a very simple application. This definitely plays a part in why I can't write code as elegant as Rust will allow it.
For the record, sorry, I don't usually like critiquing something I don't have a solution for. However, I thought this was worth bringing up. Mostly I needed to vent, but does anyone else feel similarly? Am I really the only one?
If other people are feeling the same way, I hope it doesn't become a habit for us to feel the only way to write good Rust is to allow it to be inelegant. It would be disheartening if the next systems programming language was memory safe but still noone enjoyed reading it. I'm curious what peoples thoughts are?
Mostly joking, but maybe just a line on the website or guide saying: "We take care of the performance so you can write expensive applications"?
It's almost as if many of us would rather be seen as an elite, rather than seen as a pragmatic developer.
Its not something to do before.
I will be there. Looking at your code. Judging you.
I think I've never had that issue since I started with C -> C++ -> Java -> JavaScript -> Python -> Scala -> Ruby -> ...
It was always more expensive (performance wise) to be more expressive. And I suppose since the whole community is making that performance compromise, I think it never really effected me.
Meanwhile, in Rust, the majority of the community is looking for the most performant best possible API, and it just seems overwhelming to always try to be a part of that. I'm sure I'll get over it soon. Most likely as the community grows the majority of Rust users will care less about optimal performance.
Actually, I suppose that is what happens to most languages. As the community shifts to users of the language rather than builders of the language, things might seem less overwhelming. Maybe I'm just coming to Rust too soon for ergonomics. For example in Java, there are the people that build the Netty Client / Server and they care very much about performance. Then there are most Java developers who don't really care.
Maybe if you write your code first and then modify it to get through the borrow checker it just means you're not yet familiar enough with the language's pattern (and given how young and unstable it is at the moment, I doubt anybody can pretend to know idiomatic rust at that point). Maybe if you took ownership constraints while designing your application you would end up with more elegant code? It definitely adds a cognitive load though, that's for sure.
As for the "match", "is_ok" and "unwrap" noise, it used to be a problem in my code as well but since refutable lets have been added to the language it's not really a problem anymore. For instance in my code I used to have:
match map::in_range(addr, map::ZERO_PAGE) {
Some(off) => {
// Do stuff with off
}
_ => (),
}
Or: let off = map::in_range(addr, map::ZERO_PAGE);
if off.is_some() {
let off = off.unwrap();
// Do stuff with off
}
None of which is elegant or nice looking. With refutable lets I can write that: if let Some(off) = map::in_range(addr, map::ZERO_PAGE) {
// Do stuff with off
}
For is_ok I use the try! macro which is admittedly a bit hackish but works well in practice. In the end I never use unwrap()/is_ok() outside of test code where I don't want to do proper error handling and let the runtime panic if something goes wrong.So I wanted to go deeper. I played with Go for a while, but kept an eye on Rust. However over the past couple of months, Rust just felt like they got it right. It made me care free about resources just like how Perl took care of everything, while being a strongly typed, static language with generics, and without the problems that might get in my way that Go has (GC and a heavy runtime).
If you still haven't had a look but are interested, take a look at the Rust Guide:
I think, to some extent, this is why Rust keeps being compared with Go. Last year's "Rust" was definitely something that merited comparisons with goroutines and the like. Rust 1.0 is going out of its way to make sure that it can be used for writing dynamic libraries that can be called from C or any other language with an FFI, and is possibly easier than C++ for that use case. This is completely not doable in Go (I suspect that this goal is fundamentally incompatible with having goroutines or a similar built-in concurrency story). Go seems to be a good language, but it's addressing a very different use case from what Rust is (now) addressing.
Not really. Here’s an example of writing shared libraries for C in Haskell:
Their Ruby gem is actually written in Rust.
https://blog.mozilla.org/research/2014/08/26/javascript-serv...
What are the limitations here? I'm quite a bit surprised you're able to make this work properly with pthreads... I somehow thought the GHC runtime will automatically multithread pure functions that can be evaluated in parallel.
How much can you call back into C from a Haskell function and have things behave reasonably? I suppose there's actually a bit of advantage in that such a thing would need to return a type in the IO monad, but I'm super unfamiliar with the GHC runtime.
My use case involves shipping Haskell plugins for C programs to end-users, without requiring any Haskell to be installed. Hence, statically linking all Haskell libraries into the plugin binary. On Linux, this is slightly inconvenient, as GHC packages are not usually compiled with -fPIC, so some recompilation is in order. On OS X, all code is always position-independent.
As for your other question — calling C from Haskell is very easy, and there is a lot of examples.
I'm strongly thinking that Haskell has an advantage here because "well, what if the Haskell function fails" or "well, what if the C function fails" is well-defined by the type system....
Can you return a pointer/reference to a Haskell object to C on one thread, and then call Haskell functions on it from another C thread?
A stable pointer is a reference to a Haskell expression that is guaranteed not to be affected by garbage collection, i.e., it will neither be deallocated nor will the value of the stable pointer itself change during garbage collection (ordinary references may be relocated during garbage collection). Consequently, stable pointers can be passed to foreign code, which can treat it as an opaque reference to a Haskell value.
https://hackage.haskell.org/package/base-4.7.0.1/docs/Foreig...
Then again, there is fast and scalable code, and there is actually shipping. Just because the code is very well written, counting every implied malloc() and memset(), does not mean it's worth anything if it's just sitting on your harddrive. Write code in whatever you are good at, and ship.
import rdstdin, strutils
let
time24 = readLineFromStdin("Enter a 24-hour time: ").split(':').map(parseInt)
hours24 = time24[0]
minutes24 = time24[1]
flights: array[8, tuple[since: int,
depart: string,
arrive: string]] = [(480, "8:00 a.m.", "10:16 a.m."),
(583, "9:43 a.m.", "11:52 a.m."),
(679, "11:19 a.m.", "1:31 p.m."),
(767, "12:47 p.m.", "3:00 p.m."),
(840, "2:00 p.m.", "4:08 p.m."),
(945, "3:45 p.m.", "5:55 p.m."),
(1140, "7:00 p.m.", "9:20 p.m."),
(1305, "9:45 p.m.", "11:58 p.m.")]
proc minutesSinceMidnight(hours: int = hours24, minutes: int = minutes24): int =
hours * 60 + minutes
proc cmpFlights(m = minutesSinceMidnight()): seq[int] =
result = newSeq[int](flights.len)
for i in 0 .. <flights.len:
result[i] = abs(m - flights[i].since)
proc getClosest(): int =
for k,v in cmpFlights():
if v == cmpFlights().min: return k
echo "Closest departure time is ", flights[getClosest()].depart,
", arriving at ", flights[getClosest()].arrive
[1] https://github.com/Araq/Nimrod/wiki/Nimrod-for-C-programmers[2] https://www.reddit.com/r/programming/comments/2pvf68/armv7_v...
Of interest is that it is homoiconic IIRC and not a lisp, has very good performance, and seems to have the pythonesque "one way to do it" mantra I believe.
Provided that you're not writing operating system code, or crypto code, or code with an inherently large attack surface (web browsers etc), or code which outright needs to be performant or it's useless (databases etc), or code which manages physical processes or lives, or code which deals with sensitive information (such as identifying details of real people).
If it can cost someone's job, or leak someone's identity attached to some data, or worse - lose a human life - if it breaks, writing code in "whatever you're good at" isn't good enough.
If you can't write a piece of code in Python that won't kill a person, I don't really trust you to write it in Rust either.
Hence it could actually be most secure to implement cryptographic primitives in assembler, however counter-intuitive that may sound.
(Also, how do you erase or overwrite secrets from memory after use in a purely functional language?)
Your other concern is valid, but this is easy: Haskell has mutable arrays.
Guess the StorableArray is the one that should work here.
http://en.wikibooks.org/wiki/Haskell/Libraries/Arrays#Welcom...
You should be able to exploit this quite easily if what you said is true, right?
That's why we go off of metrics like long periods of use. However as we saw with heartbleed even that metric is flawed.
I had a quick poke a few months ago, and found that a lot of the documentation was conflicting (one of the key resources everyone talked about was so out of date it was unusable) and unhelpful, because of the rate the language was evolving at.
I'll get started with it about 6 months after version 1.0 is formally released, I think. Hopefully, the docs will have stabilised by then.
Is this today, or months ago? Note that a few months ago I _completely re-wrote_ what was then the Tutorial, and it's now the Guide.
If you have any specifics on the current version, I'd appreciate some GitHub issues :)
The basic concept of ownership in Rust is simple. Everything has either a single owner, or a reference counted cell as owner. Things which cross thread boundaries have a locked reference counted cell as owner. Temporary references to single-owner objects are allowed, but must have a shorter lifetime than the primary ownership, and this must be demonstrated by simple static analysis. That's straightforward.
Making those restrictions usable seems to require many features and much churn within the language. It's too soon to tell how this will come out. As I'm mentioned before, I'm bothered by the need for "unsafe" code in pure Rust that's not calling hardware or C code.
http://doc.rust-lang.org/src/collections/binary_heap.rs.html...
This is exactly how I'd expect to see `unsafe` used: as an additional optimization in situations where you can't get better in any other way, and with a big-'ol comment explaining exactly why. Well, one of the ways, at least.
I am very suspicious of unsafe code for claimed "performance" reasons. That leads to exploits and backdoors.
A swap could easily be a exchange of e.g. 16 (the size of a slices &[T]/&str on a 64-bit computer) or 24 bytes (Vec<...>/String) (or larger), and there's no way to use single SIMD instructions for these in general.
Also, "per operation" is vague, since the swaps would have to happen repeatedly in a loop, so a single .insert call is likely to result in multiple swaps.
Why is this bothersome?
I'm curious, as to me the advantage is not in full-program safety, but in having a distinction between portions of a program that are guaranteed safe and portions that aren't.
Or, to turn it another way, Rust isn't giving you guarantees about 100% of the code you're looking at: it's vouching for the >0% (hopefully!) but <100% that's marked safe.
It seems an elegant, realistic engineering solution to a hard trade-off between performance and safety.
Static analysis for pragmatic reasons is really common in Rust even now.
(BTW, unsafe {} is still a whole lot safer than standard C)
Could you get more specific for me, please? There's certainly much room for improvement, but I can't fix what I don't know about!
> If you have to clearly explain in writing how something works,
Are you aware of our RFC process? Changes _do_ have to have extensive, written explanations.
The API docs + search in the docs are incredible IMO. I'm in absolute love with rustdoc. Having links to the source right there is the most valuable thing ever, both when trying to understand how to use a given API, and when trying to write similar things its really useful to be able to look at how others have done things.
That said, if I may say so, string handling should be more throughly detailed. It's one of the first thing new comers will stumble upon. Especially for those who come from a dynamic language. Heck, I think it might even been the perfect occasion to exemplify the borrowing system.
Edit: Just some further thoughts on the spot (feel fre to ignore if you strongly disagree).
It feels at the beginning that the guide is kind of building things up. In that spirit I would put an expended String chapter just after "7 Comments" since simple example programs can already be made without what's following (well except maybe loops). Maybe something in the spirit of "14 Guessing game" but smaller. That is, with less, more recently read, concepts to refer to.
(And you're right, the Guide is intended to only get you to mid-level stuff)
I also think there should be a consolidated guide, which users can download as PDF and maybe print out (Maybe I'm somewhat old-fashioned in this respect) -- an always up-to-date go-to reference that explains the ins and outs of the language. I don't want to browse 20 guides/tutorials that may or may not contain the info I'm looking for.
Have you seen "The Guide"? It's about 100 pages long, and we used to produce a PDF, at least...
This suggests to me that the most anti-sloppy languages like Rust would benefit the most from sloppy input inference techniques. Has anyone been working on developer tools to make writing Rust code easier? (Lifetime management is the obvious new language feature to target.)
[1] http://groups.csail.mit.edu/uid/other-pubs/sloppy-programmin... (pdf), or less-detailed: http://groups.csail.mit.edu/uid/projects/keyword-commands/in...
If you haven't toyed with Rust since late summer when Lifetime elision[1] landed, it's very worth revisiting. That feature made writing out lifetime notations unnecessary in 87% of the standard library. It's made things a lot more pleasant. There's still plenty of room for IDEs and editors to help out, of course, but the language itself is making great strides in usability as it matures.
[1] https://github.com/rust-lang/rfcs/blob/704f0060176418659698e...
Incidentally, the 87% number is a bit misleading. Before the Lifetime Elision RFC, a limited kind of elision was already allowed (when a function borrowed a value but didn't return a borrowed value). The 87% number was the number of lifetime annotations that were still required even with that limited rule that could be removed with the new rule. When taking all kinds of elision into consideration, the number in the standard library is closer to 95%. (many of the remaining cases in the standard library involve the implementation of collections)
In practice, that means that the vast, vast majority of borrowed references don't require explicit lifetime annotations, and that is pretty close to 100% in "application" code built on top of abstractions.
I'm not sure if anyone has done much work to narrow this divide. There are some COQ IDEs out there now, but I'm not sure how effective they are in relieving cognitive burdens while programming.
The irony of the situation is that languages like Rust and Haskell provide a lot of static type feedback that theoretically would make their IDEs very powerful. However, in order for that to really happen, IDE concerns have to be considered very early in the language design process. As a result, we see the tooling crowns going to languages like Dart, whose "type system" is basically designed to make tooling possible (and secondly, for the early detection of errors).
I tried to revive as much information as I could from my days in Visual Studio, but if you have additional feedback, I'm sure he'd be very interested to hear it! Obvious caveats about it being a long-term goal, no immediate plans to tackle it, larsberg is on Servo so what does he know, etc. :-)
Rust's type system doesn't really seem to be there yet: there are just "wrong programs" and "right programs" with not much in between. Gradual typing on the other hand works very well here.
As it turns out, very few people are interested in having software that works.
(Moreover, rust itself may have bugs, or there could be bugs in unsafe code in the standard library. The amount of trusted infrastructure is much smaller for SEL4, I suppose if you compile it with compcert, I imagine it's quite small)
The worst case latency is also part of the proof for SEL4-- which is a kind of correctness you cannot achieve via rust (even ignoring overhead).
There are many elements of program correctness beyond memory safety. The developers of rust generally eschewed concerns about other kinds of correctness. For example, your integer types may overflow and your algorithms could fail to work as a result. SEL4's proofs show the operation of the software match its specifications completely (given some assumptions), it's really far far beyond the memory safety promises of rust.
For any embedded work or kernels, you wouldn't be pulling in the standard library via a `#![no_std]` attribute.
But you're right that bugs in the compiler could produce safety issues.
Perhaps not, but in a kernel you'd have plenty of your own unsafe code.
SEL4 doesn't.
There are, of course, people thinking about bridging the worlds of formally provable correctness and Rust, e.g. https://github.com/rust-lang/rust/issues/18496 though extraction based is likely to not produce the most elegant or efficient code... and I assume the along-the-side proofs like SEL4 uses would be harder for Rust because the language is more complex than C.
The Rust compiler can (and does) have bugs, but they get weeded out over time. But so can whatever proof system or proof checker is used on SEL4.
As for integer overflow, yeah...that's a hard problem with Rust, and unfortunately, unlikely to be fixed.
A Rust based kernel could be a *nix, and do everything from desktops to server. It would be developer friendly in the same way Linux or Windows is. Plus, the baseline safety would be much higher with a Rust based kernel, without having to formally proven everything.
Instead of having all the services required for an operating system in the kernel, the kernel just implements the bare minimum including fast IPC. Things that traditionally are implemented in the kernel (filesystems, device drivers, other modules, etc) are all implemented as services living in userland and are assigned the minimum necessary additional permissions for them to perform their task.
You can build a *nix like system on top of seL4. You can even run Linux itself on top of seL4, with L4 acting as a hypervisor.
An operating system doesn't consist of just the kernel.
No. Most of the huge development was in developing proofs for C. The developers now find that development is easier than just using C directly. The process is to write it in a high level specification language, then when you're done messing around and know you have it how you want it, translate it to C by hand and the proofs will tell you that you did it right (or wrong).
>It doesn't seem to have the feature you expect from kernels these days.
It has exactly the features we expect from kernels these days. The features we do not expect from kernels these days but did expect from them 30 years ago are available in a library where they belong.
>A Rust based kernel could be a *nix
So can any L4. Why would "start from absolutely nothing" be easier than "start from an existing microkernel"?
L4 is fantastic, by the way.
No it won't, that's the point. It is a much stronger guarantee of being without those bugs than using rust would provide.
>because it would have far fewer segfaults and the like
SEL4 can not segfault, that's the point. It isn't fewer, it is zero.
>but clear I was thinking that we can start with a Rust kernel and build on top of that.
Yes, and I am saying if people actually wanted that, they could start with a formally verified kernel that already exists and build on top of that. But they don't.
Presumably you'd want formally verified versions of at least the network device driver... otherwise process isolation might be violated.
The reason very few people care is that "works" is not a binary, but obviously a continium -- and people inherently understand diminishing returns...
Especially when the "works" they care about is getting shit done, by having access to software (including proprietary), drivers, a community, vendors, etc.
The "is formally verified" they could not care less, and rightly so, if their other demands are not satisfied...
That's however orthogonal to what I wrote, I think.
It's not like it has a software ecosystem, community, drivers, etc, to be adopted over any major OSes as something that "all others being equal" is also "formally verified".
And yet your post can be summarized as "I am not aware of the usage of L4 microkernels".
>The "is formally verified" they could not care less, and rightly so, if their other demands are not satisfied...
Except their "other demands" are satisfied. It implements L4, just like every other L4 implementation does. It also happens to be the fastest L4 implementation ever tested. So rather than vague dismissive "calling BS", how about you point out the specific fault in SEL4 that causes people to need to use other L4 implementations instead?
I'm talking about people in general, prefaring their mainstream OS of choice over some platform based on L4.
Not about L4 users opting for this or that implementation.
The parent talked about people in general not caring for software quality, while those L4 users are highly specialized enteprises and businesses making a choice.
Not necessarily. If there is a formal model of a specification, and a piece of software provably implements that specification, then that is an example of software that works in a very clear, not-a-contiuum sense.
Yes, and we know what the quality there is.
If that's your attitude, you probably don't work in or near the industries that would have the budget or requirements to care about the seL4. I have, and it would be considered, without a doubt.
It takes a while to write a stable OS, because a lot of things that we use in our helpful libs can't be used in Kernel space. I'm thinking of taking some time and trying to help a Rust OS project.
http://src3.org/#RHEL6-2.6.32+220.el6/lib/
or for example Linux Kernel "string.h" (with fun ifdef) http://src3.org/#RHEL6-2.6.32+220.el6/include/linux/string.h
I'm sure reaching Rust 1.0 will speed things up.
Someone posted their Rust kernel https://github.com/lexs/rust-os
It would take a very long time for that to be even possible. The hardest part of writing an OS today is more about the amount of hardware out there to support and getting users. There are a couple of approachs to mitagate this thou.
1) Work well in a niche, this has worked well for L4, QNX, VxWorks etc, but doesn't make the OS very visable to most people.
2) Run on top of an existing virtulisation plaform to minimise the hardware support needed, butby doing that you also reduce the impact of working in a safer language.
I guess you could argue that "better" doesn't have to mean relevent to end users/devs, a bit of an ivory tower approach thou. Otherwise you are looking at several years of work
- if you like C or Python, you will probably prefer Go
- if you like C++ with templates or D or Ruby (especially Rails), you will probably prefer Rust
We will see how the languages evolve, but so far I really enjoy Go having native channels, having a stable language definition, and a simple yet stable and powerful standard language API. Also the public/private access system based on capitalization is genius, and the package management is very intuitive.
The fundamental difference between the two schools is in how they view corner cases. The New Jersey schools suggests punting entirely on corner cases to keep the design simple; the complexity is either offloaded to the developer, or the language designer says that the language is simply not suited for that problem domain. So you have the Go devs saying that you just don't need generics or Guido van Rossum saying you don't need execution speed. The MIT school says "We should solve the problem and make things easy for the programmer, goshdarnit", and so you get things like the STL (where every algorithm is built for maximum generality at no performance cost) or Rails's auto-pluralization of database tables. Or another way to look at it is that the New Jersey school asks "Why?", while the MIT school asks "Why not?"
You can extend the analogy out to other languages: C, C++, Java, Python (borderline), Objective-C, Go, PHP, Javascript, and Perl are New Jersey school languages, while Scheme, Common Lisp, C#, Ruby, Dylan, Erlang, and Haskell are MIT school languages. Note that they very often occur in pairs (C <=> Scheme, C++ <=> Common Lisp, Java <=> C#, Python <=> Ruby, Objective-C <=> Dylan) with a similar purpose and timing in the market, but generally the New Jersey school language does better commercially. It is not universal, though - Python and Ruby are both commercially successful but in slightly different niches, as are Java and C#. Usually when the MIT-school language is successful, it's because they manage to differentiate themselves with a strong killer app or framework (eg. Rails for Ruby, .NET for C#).
...now that I'm rereading it again, I wonder if my original phrasing was better. The idea that I want to capture is that there's a difference in how these languages approach corner cases: the C/Python approach is to punt and say "We're not designed for that", while the C++/D/Ruby approach is to add a feature to the language to solve that one case. C++ is basically what happens when you extend a New Jersey school language to cover the corner cases that it originally chose to ignore; that doesn't actually make it MIT school (which is more a philosophy on how to design systems, not how they end up), but it does put it in a different category from C and Python.
I think your split of "Worse is Better"/"Better is Better" languages is a bit arbitrary. Everyone agrees that C is a Worse is Better language, but the rest? In particular, I cannot decide whether you equate OAOOW with Worse is Better or not -- Python is OAOOW, but I wouldn't say it's a Worse is Better language (and claiming it is because "you don't need speed" is like saying Haskell is a Worse is Better language because "you don't need the ability to easily reason about runtime performance". Every language claims "you don't need X"; that's clearly not enough to classify it as WiB).
https://www.dreamsongs.com/WorseIsBetter.html
Complexity creeps in too quickly and too easily. While I don't like the WiB moniker, I definitely prefer OWLs. :)
I also don't think it's right to think of Rust as a better-is-better language in philosophy. Many folks coming from languages like Haskell find Rust full of design choices that seem at odds with its "functional programming" reputation: lack of higher-kinded type parameters, for example, lack of an IO monad (or the use of the term "monad" at all), a willingness to have subtyping in the language, heavy use of macros, etc. etc. The reason is that we actually have a heavy bias toward pragmatism and simplicity wherever possible.
It's our constraints that required the language to have the concepts that it does (performance-competitive with C++; no global garbage collection; data-race free; runtimeless/embeddable). We simply couldn't have copied another language and said "here's our language, now let's write a browser engine in it". It would not have the performance characteristics or the safety characteristics we need. It's not a difference in philosophy so much as in requirements.
FWIW, I'm glad that Rust didn't choose that path, as I've tried Go out and found that it really didn't work for me as a Python substitute, and am now dreading the day when I (hopefully) will have to rewrite parts of my startup in C++ for performance. I'm actually seriously considering Rust as a substitute here, but would like to see more stability in the core language before investing serious time learning it.
But I'm also someone who has never shied away from complexity in a language before, which probably puts me more in the "likes C++, Ruby, and D" camp even though I chose Python as my main scripting language. One of my main concerns with Rust is whether it will be possible to hire & manage developers who can work effectively in it, since I know that not everyone is as willing to learn new languages and language features as I am.
And functional programming languages are just arbitrarily complex and obtuse because it hates its users? No, it simply has different constraints, like the following...
> It's our constraints that required the language to have the concepts that it does
And it's Haskell's constraints that lead to it having to be a pure language, and so on. Rust is just opinionated in a different way, and because of different constraints. Do you want functional programming in Rust? - it's probably not going to be as accommodating in that regard as a functional language. Do you want to have zero-cost abstractions in Haskell? - the language is not made with that in mind, so you will probably have to wrestle a lot with extensions and custom abstractions in order to achieve that, if it is even possible.
What are you basing that on? C++ is a "lets add everything and let the user sort it out" language, how is that in line with the principles of the "MIT school"? Ruby is literally perl--, how is that MIT school? Because rails does incredibly dumb stuff like incorrectly pluralizing table names by default? That's not MIT school, it is just dumb.
>and I assume that Rust is in the MIT school.
I can't imagine any way to rationalize that idea. Rust is at its core all about trade-offs and "good enough"s. It is supposed to be a better systems language, not an ideal language. I can see claiming scheme, or SML or haskell or clean are "MIT school". Basically any language people spread FUD about being "not practical". But rust is explicitly intended to appeal to the people who pretend good languages are not practical.
Go in my opinion is the best proof that our industry is fashion driven.
It's not though. What is fashionable about Go? Most testimonies I've seen state solid reasons for choosing it. Nobody claims it to be a marvelous language like Haskell. It's a simple language that's easy to pick up, implement an idea, and deploy. Much like python, but with much greater performance.
I don't see what fashion has to do with it.
No, it didn't. In addition to the already-observed point that Go's concurrency story has always been better than OCaml's, Go also offers it to you in an Algol-esque syntax. It isn't C, but if you know C the differences are dialectal, whereas OCaml is from the ML tradition and is radically different. ML is the language family probably most distant from C in current medium-scale usage.
I don't like or celebrate this fact, but it seems that broad-scale success right now requires that you be an Algol-descended syntax.
> Go in my opinion is the best proof that our industry is fashion driven.
Go itself isn't flashy or trendy or fashionable. It doesn't introduce exciting new concepts. It's as notable for what it doesn't include as what it does include. It is, in my experience, an extremely practical and pragmatic language. Saying it's proof of a fashion-driven industry is like saying the Honda Accord is proof that the automobile industry is fashion driven.
I believe some of the initial interest in Go was due to its newness and its affiliation with Google. But traffic-baiting bloggers have moved on to the next thing, as have programmers who are more focused on the process of programming (i.e., "I want the act of programming to be fun") than the results. I think it's settling into a comfortable role as a practical choice for teams who want to get work done.
This is a general statement, it's not always true; but the control Rust provides theoretically means one can always wrangle the Rust code to be equal to/faster than most other languages.
(In fact, the embeddability of Rust means it is pretty well suited to writing the parts of a Python/etc application that need performance, since Rust can easily make dynamic libraries which can be loaded as extension modules.)
1. https://news.ycombinator.com/item?id=8035293 2. https://github.com/hackndev/zinc/graphs/commit-activity
That said, it's definitely a thing the Cargo devs are thinking about: https://github.com/rust-lang/cargo/issues/1044 https://github.com/rust-lang/cargo/issues/837
(There is also at least one recent paper on Rust which is t linked here.)
I implemented a completion based wrapper ontop of libev and libaio in C but could never quite nail it.
It's worth mentioning that IOCP can be used on any fd in Windows, whereas Linux epoll loop can only be used for sockets. There is a caveat to that however, you can use the epoll loop if you use eventfd with some effectively undocumented behavior of the aio subsystem that allows you to register an eventfd with your aio context. There are drawbacks to this though, you have to use O_DIRECT and aligned writes. In Windows you can use IOCP to do async buffered writes...
Thankfully much smarter people work on Rust and hopefully they manage to make a completion based IO system that is portable across the operating systems.
Think about this the way that you might think about GCC backends -- some architectures make certain things easier than others.
Or how an abstraction layer like MonoGame papers over the fundamental differences between OpenGL and DirectX -- some abstractions perform better with one or the other subsystem.
Certainly, code rewriting to support continuation is a factor in both C#-style await and generator functions, but they are not the same thing.
Much of the motivation behind async I/O is to reduce the number of active threads blocked on I/O. Blocked threads means stack space consumed, kernel transitions, and potentially CPU contention if too many threads become runnable at once, and then start fighting over mutexes or cache.
In this way of looking at the world, you don't want to block for any I/O. That's what the GP is getting at.
a) Be able to await the result Future/Task objects instead of attaching callbacks to them. This is done by source code rewriting and has no dependencies on an IO system.
b) Having IO libraries that return future objects. You can do that with IOCP as well as with unix poll based architectures. Might be a little bit easier with IOCP, but it's no deal breaker. I already did an implementation for epoll once. You simply spin up an eventloop in any thread. Any IO read/write returns a future object, posts the work to the eventloop and as soon as the work completes the eventloop completes the future.
I think the original Rust did not even have a need for async/await because it used "lightweight" tasks for that purpose. But now these are out.
It's definitely possible in a strongly, statically-typed language (I believe C#'s async/await is an example), but Rust doesn't do it... yet: https://github.com/rust-lang/rfcs/issues/388
http://bluss.github.io/rust-itertools/doc/itertools/macro.ic...
Oh, and datastructures that need cycles (the libstd's doubly linked list, for example), can use raw pointers to manage it manually, encapsulated in the implementation.
There's also http://featherweightmusings.blogspot.com/2014/04/rust-for-c-... which is longer, but not as useful.
Rust has roughly the feature set and memory model of C++. The difference is that in Rust, the safety issues C++ sweeps under the rug are addressed. As I point out occasionally, the three big problems in C/C++ are "who owns it", "how big it it", and "who locks it". C fails to deal with any of those issues. C++ sometimes deals with "how big is it". Rust deals with all of them.
In Rust you have to deal with most of the memory management headaches you have in C++, but they're detected at compile time, not after the product ships.
In retrospect...