Rust vs. Go
blog.ntpsec.org
blog.ntpsec.org
> For comparison, I switched from Rust to Go and, in the same amount of time I had spent stuggling [sic] to make even a <100 LOC partial implementation work, was able to write and test the entire exterior of an IRC server - all the socket-fu and concurrency handling - leaving only the IRC-protocol state machine to be done.
I don't think this is an unusual experience. I'd consider myself a journeyman programmer with 16 years of experience and probably a dozen languages under my belt. Learning Go was unique: after 3 days I felt productive; After 2 weeks I sort of looked around and said "is this it?" Go gets out of your way for the most part, and performs as advertised.
For a lot of programmers, for a lot of projects, that's exactly what you want.
I've attempted to learn Rust a few times, and it always feel like I'm moving uphill.
Rust is awesome for a number of problem spaces, so I'm not knocking it. And Go _isn't_ awesome in a lot of ways (vendoring, I'm looking at you). But it feels like there are a lot of core, language-level things Rust needs to improve to attract more non-genius developers.
A large part of the reason Javascript has taken off is it's ability to "get out of your way", however you pay for that in the long tail of issues and undefined things.
Many projects in software are trying to prove out a concept, and in those cases is makes sense to move fast and find out if the core thing you're doing works. However in problem domains that are well understood or have strict performance/memory requirements Rust really shines.
I think there's a strong argument that this is a subset of software(and probably not the majority) however I feel that unless you're taking a project to 100%(remember the last 10% takes 90% of your schedule) ESR's comparison isn't fair.
So, I guess you can "ignore" stuff in other languages and feel like it's working, with less guarantees it is actually right. Most of the things you need to do in C, Rust just codifies and enforces.
What dynamic languages have as an advantage is that they allow you to more readily change your system as your understanding of the requirements change. This is where static type systems have traditionally been very weak - they create roadblocks for evolutionary change.
What would be great is a type system which catches your bugs but doesn't block you from evolving. I actually think Rust's use of type class polymorphism ("traits") goes a long way toward that goal, once you learn it.
As an aside, I keep seeing people don't "get" this, and it is painfully unclear to me - why?. Perhaps the pool of people with 15-20 years that are still doing programming is very small, so they make very tiny noise.
When I tried Go I just didn't get what it had to offer. It felt like the wheel, reinvented. I was like "That's it?". I can see how that's appealing but, for me, Rust is a stepping stone on what we should expect from programming languages of the future.
A new language with a new paradigm is going to take longer to feel productive in, especially when the tooling and libraries aren't quite there yet. I can see how that's not good for someone who wants results now.
Rust's ecosystem is very much in its infancy. I expect 2017 to be a great year in terms of both language ergonomics (e.g. see Rust Language Server) as well as stabilization/standardization of very important things like how to do concurrent I/O. If your niche is distributed computing (e.g. I/O bound servers), yes, Rust is currently not going to satisfy you.
I think comparing Rust and Go is not fair for neither of them and, even if both are advertised as "systems languages" they are actually very different beasts filling their unique niches.
I'm at 20 years of paid, professional coding, and 30+ if you count my Apple IIe/gs days and fooling around with C++ as an undergrad.
It took me about a week or two to make friends with the borrow checker, and mostly those were self-inflicted wounds from trying to write aggressive zero-copy code.
I think that Rust works very well for certain coding styles, but the learning curve is much higher for other coding styles. If you've been scarred by C++ and you're used to treating variables as mostly immutable, the learning curve is pretty reasonable. But if you work in a style with a lot of mutability and you've never wrested with "const char *" versus "std::string" in C++ (which both exist for a good reason in C++), there's more to learn. So I could legitimately see it go either way.
My handy litmus test: Do you utterly despise C++, or do you have a complicated love-hate relationship with it? If you fall in the latter group, you may fall in love with Rust on the first try. If you've never even used C++ enough to have an opinion, then it's possible you might not care about high-performance static languages and the prices they pay to go fast, and so Rust's trade-offs might not even be interesting to you.
Maybe I'll give Rust a go after all. No pun intended. ;)
[0] Unless that something is Rails templating because dang, that is some crazypants there.
I'm a pythonist most of the time, although I've done a not insignficant amount of work in C, but I like high level languages more, and C++ feels like the worst of all the worlds. It is complex and bloated (templates) and magical and all kinds of things. I think I summed it up one day by saying that, as a newbie to C++ my frustrations with the language were summed up by a 0 argument object constructor having no parenthesis if it was stack allocated, but heap allocation or arguments require parens, because the 0 arg constructor would conflict with a function prototype if it had parens.
There's so much bloat and cruft and oldness that the language has baked in inconsistencies and gotchas and so much stuff (which of the 12 different kinds of pointers should I use) that it is horrible to learn.
Rust on the other hand has complex semantics, but they are well defined, sensible, and logical. Sure I sometimes want to smack the borrow checker, but the reason why isn't "someone made it this way before I was born and now I'm stuck with the results".
Getting dependencies from the system is a big PITA and the build tools, as in cmake, seem to require as much knowledge as the language itself.
Having a C++17 with a tool like Cargo building static binaries and just the right versions of the libraries would make my life with the language much easier.
The most important tricks I've learned are:
- Return types should usually be owned values, input types should often be references (or AsRefs). "Be explicit in what you return, and general in what you accept".
- If I'm going to be pointing into an array, its often easier to just pass around indexes instead.
This is a terrifically powerful, and very unusual, advantage for a language to have. Master programmers won't be choosing it as their tool of choice. It won't be used to attack the craft's hardest problems. But it allows a huge number of people to write programs they otherwise would not have been able to write, and, conversely, a huge number of programs to be written which otherwise would not have been written. There's something rather "quantity has a quality all its own" about it. As with PHP, this is something of a mixed blessing, but it certainly puts a dent in the world.
I see almost the opposite, with many new distributed systems being written in Go.
But every time I need to dig down into the source code, I quickly realize why Docker falls over all the time with weird bugs, and why Vault's high-availability backend for etcd falls over once per week, and why Kubernetes is capable of inspiring such epic love-hate relationships.
There's a ton of code in Docker where nil can sneak in, and where the types of certain data structures are harder to determine than they should be. Sometimes nil represents "none", and sometimes the empty string does. There's a bit of multithreaded stuff in Vault's etcd backend (or was) that doesn't understand the corner cases inherent in concurrency.
I'm totally convinced that these new distributed tools are awesome and a boon to our industry. I just wish that somebody had a long conversation with a good type checker and thread safety checker at critical moments during the implementation process.
This may be a classic case of the "New Jersey approach": https://www.jwz.org/doc/worse-is-better.html Software that's sufficiently innovative and which works will be forgiven odd corner cases and bugs.
Vault's documentation clearly states to not use ETCD as the HA backend.
Docker's problems stem from their bone-headed decision to start with a client/server architecture that they've been unwinding ever since. They should have started with small composable tools that integrated well with the existing infrastructure (init systems, file distribution, and security infrastructure). Instead they started with an architecture where everything goes over an HTTP socket through a daemon with high probability for contention, deadlock, and failure. None of that has anything to do with Go. They could have built that abomination in any language.
I can't speak for Kubernetes. I have not used it in anger yet.
Yes, when I reported bugs against the etcd HA backend they said they were going to add more prominent warnings to the docs, if I remember correctly. :-) I think the underlying bug comes from overly optimistic multithreading code using CSP, but I stared at the code for several hours and couldn't see how to make it unambiguously correct.
We now have our own in-house Redis "HA" backend that I wrote which appears to be rock solid in production, but which temporarily disables Vault during Redis restarts. So it's really "fake HA" but it works nicely for our use case and never requires manual intervention or embarrasses us during business hours. I refuse to set up and administer an entire Consul cluster for a single leader-election lock. Managing Consul has its own complications: https://www.consul.io/docs/guides/outage.html
> Instead they started with an architecture where everything goes over an HTTP socket through a daemon with high probability for contention, deadlock, and failure. None of that has anything to do with Go.
The Docker daemon's wire protocol suffers from a kind a of sloppy thinking about data types, in my opinion. Consider this line in a Rust Docker client: https://github.com/faradayio/boondock/blob/bf29afb0c78f8d5d6...
pub Ports: Option<HashMap<String, Option<Vec<PortMapping>>>>,
Here, the `Option` types say, "This might be a JSON `null` value on the wire." So the `Ports` member might be `null`, or it might contain an empty hashmap, which in turns contains values that might be `null`, or might be `PortMapping` value. The corresponding Go code is extremely unclear (at least in most similar situations) as to which values can be `null` on the wire.There's a lot of really foggy specifications going on in the network layer of the Docker server. It's not bad in the way that certain old Unix servers like `csvd` were bad, but I feel like Docker could be better if Go demanded more clarity about data types.
On the other hand, Docker is clearly a raging commercial success, and it actually works quite nicely (at least if you use Amazon's ECS to manage your clusters). So maybe I'm just being overly fastidious. Clearly all this new Go-based devops infrastructure is hugely successful, even if many of us get frustrated with it now and then.
Still, I really wish that more programming languages forced you to be clear about which values can be `null` and which can't. Hoare called it his "billion dollar mistake": http://lambda-the-ultimate.org/node/3186
Ultimately it's the architectural decision to couple everything that allowed them to be loosey-goosey with internal-only interfaces. Why bother specifying them thoroughly if you can update all components simultaneously and have no dependencies outside of your control? I'm sure a tighter language would have helped in the small, but their problems are categorically big problems that I believe transcend smaller and more localized failings.
My experience using Consul for the Vault backend has been pretty good. Yes, you have to manually go in and clean up old nodes after a restart, but that isn't too bad. So far (knock on wood) we haven't run into a situation where a replacement node has refused to join the cluster or the cluster has out-right failed.
That said, I do wish they would put the effort to properly integrate with Etcd. We've gone through the motions of setting up and managing a highly-available Etcd cluster for quite some time now and are more confident running it than Consul. I'd rather standardize on Etcd myself, but I'm not inclined to swim upstream. :)
Docker's faults do not come from Go, and the same goes for Kubernetes.
Rust Roadmap says this much for production use:
"Production use matters for the obvious reason: it grows the set of stakeholders with potential to invest in the language and ecosystem. To deliver on that potential, Rust needs to be part of the backbone of some major products.
Production use measures our design success; it's the ultimate reality check. Rust takes a unique stance on a number of tradeoffs, which we believe to position it well for writing fast and reliable software. The real test of those beliefs is people using Rust to build large, production systems, on which they're betting time and money.
The kind of production use matters. For Rust to truly be a success, there should be clear-cut reasons people are employing it rather than another language. Rust needs to provide crisp, standout benefits to the organizations using it."
https://github.com/aturon/rfcs/blob/roadmap-2017/text/0000-r...
None of those languages strike me as being both elegant and state of the art at the same time.
Meanwhile the "weapons for a civilized time" such as Lisp or Haskell haven't really gotten a major hold in either a Fortune 1000 company or the software giants on the West Coast.
It's like there's no correlation between "hacker points" for a programming language and the actual results delivered with that language! I'm being ironic, there isn't any. I'm open to someone proving me wrong though...
Not that this defends the very poor syntactical choices that Lisp and Haskell and their communities have done (minimal grammar is not a good thing! One-symbol names are horrible!), but a lack of popularity is not as damning as it sounds.
Ecosystem is part of technical merit, but I'd agree that in many cases (particularly non-software firms), languages aren't chosen by technical merit (including ecosystem), but for social reasons.
Last I checked, the list of publicly acknowledged production Haskell uses included several significant ones in Fortune 1000 firms, and various Lisps have seen production use at major firms, both inside and outside the software industry.
To pedantically respond to your question, note that what i said was that master programmers "won't be choosing it as their tool of choice", by which i mean that it won't be the language they dream about being able to use on their next project. Of course, if Go is the right tool for the job, a sensible and honest programmer will use it.
That's a fair point.
Don't get me wrong, it a great, practical, engineering-oriented 90% solution kind of language; "conceptually sound" seems like computer language theory got involved, and it didn't.
My experiences discussing anything Rust related with you has generally gone like this: I list several things I think are problems. You refute a small nit in only one of them and neglect the rest of the message. Giving the impression that you've successfully refuted everything I've said, you tag on the stick-your-tongue-out smiley :P
I've had other discussions with other core Rust developers. As should be expected, the conversations go differently for each.
I started to combine channels freeform and enjoy new ideas every minute, almost no cruft, almost no dead ends ... that's a rare thing.
They did circle the concurrency pretty neatly in a nice usable interface.
It's worth noting here there is a large, active team from the open source community working on fixing vendoring and package management right now. I believe the target is the Go 1.9 timeframe (end of Q2ish this year).
Go is an easy, fast, and very parallel language. Go is exactly what google needs, in all regards. It makes sense why Go exists, it makes sense why google is creating a way to migrate from python2 to go. Go is easier than hard C and Go is fast.
Rust is the safe follow-up of C, but more familiar than Haskell, Caml and such. It's a pain to get working, it sucks to program in, it requires so much thought, it's just a bloody fucking fuck. But once rust runs, it works and there's a very very good chance it won't break.
And that's also the reason why we'll probably build firewalls and hardened OS in derivatives of Haskell and Rust, and we'll build Google 2 and Facebook 2 in Go. And in my book, that's a good thing.
I could not disagree more! Rust is a great joy to program in. I would rather program in Rust than any other language.
Rust is challenging for many people to learn. That is very different.
The borrow checker and ownership model is a huge tradeoff. Even those very experienced in it still fight it from time to time. Go made a different trade, they chose GC. And GC _is_ easier. But it has computational overhead (well today anyway).
Consider the cascading implications. Rust has a trait for Copy vs. Move types. This means every type has an implicit, and unknown behavior, on certain assignments:
let v2 = v;
println!("v is: {}", v);
You can't actually tell me whether the above code compiles without consulting other parts of the code. That's not the case for Go. It is thus simpler and more intuitive to program in.But it is a trade-off. Go as a language must have a GC. It will never be suitable for certain tasks as a result.
Go also has less compile time checking. This means faster compiles, quicker dev cycles but less compile time safety. Go chose easier here again. These trade-offs are what make rust seem "difficult" in comparison to go. I don't know if you can ever "fix" them.
If you operated a nuclear reactor with this mentality, I'd be concerned. If you're operating acme.org with that mindset... godspeed?
I find the ownership model has non-performance advantages in that I find it provides strong design pressure to factor my system well. I would not describe my experience as 'fighting' the borrow checker - I feel positively when the borrow checker sends me back to the drawing board, because it teaches me things about my system I had not properly comprehended.
(There are areas where the borrow checker is overly conservative, but I never hit these in practice.)
> You can't actually tell me whether the above code compiles without consulting other parts of the code. That's not the case for Go. It is thus simpler and more intuitive to program in.
I'm sorry this is just silly. Here's some Go code:
v2 := v + 1
You can't actually tell me if the above code compiles without consulting other parts of the code, because you don't know if `v` can be added to `1`. In Rust, type inference is only local to a function, so you will be able to determine from an actual compile-able code snippet whether or not that use after move is valid.Go also actually might not compile on the exact example you cited (translated to Go, of course) because that code contains an error if `v2` is never used after it is assigned.
In general, you cannot reason about the correctness of incomplete code snippets. This is not specific to Rust.
The ownership and borrowing model may provide many benefits, but those benefits come at the cost of complexity (as defined by Rich Hickey in Simple Made Easy). I have never found the owning/borrowing thing to be very hard. But it is _more complex_. There are _far_ more fundamental concepts baked into Rust then Go. And the interplay of those concepts creates complexity. It requires more knowledge and inherently complicates more things.
The parent that started this says "it requires so much thought". What that means is they have to hold a lot more concepts in their head. It's not that any one concept is hard. It's that holding all those concepts and the interplay in their head at once is hard(er). This is what people mean when they say "Rust needs to improve to attract more non-genius developers."
A huge portion of things you have to think about in the ownership/borrowing model simply doesn't exist with a garbage collected language. You just _don't_ have to think about a huge set of things at all.
Type inference is an example of this tradeoff that both Go and Rust agreed on. This adds complexity and decreases clarity in both languages. The upside is expressiveness and conciseness. We could always make you specify the type with a declaration (like C) and then the snippet you posted would need less external reasoning:
int v2 = v + 1;
While you can't fully reason about any snippet of code, the more core language level concepts I have to address with any snippet the _harder_ and more complex it is for me to consider them.*I'm _not_ trashing Rust here. I'm just stating that some of the good tradeoffs you made here leave the language inherently more complex and thus difficult to use. There are _upsides_ to that. You gain safety, performance, and "pressure to factor [your] system well". Simplicity is very far from everything.
When you're writing acme.org most of those tradeoffs are may not be worth the complexity. When you are writing a safety critical system they damn well are.
*PS on the subject of code snippets and compilation. As engineers, the primary audience of our code is other engineers. As such, I always find some programming language debates have real insight when addressed by my Strunk and White (no really). Consider the following two sentences:
The compiler must know the lvalue. It may then use it later on.
Too me this is the English language equivalent of type inference. We know "it" is "the compiler" based on previous statements. Using pronouns too often in a sentence makes it unclear. Using them too sparingly makes it tedious (and also often difficult to read).
A similar balance holds with concepts the reader must address in a given paragraph. This is the line we all dance when we write code. How many concepts must we hold at once to comprehend what is written?
It seems a bit like pointers, higher level languages will hide these details from you but you still have to know what they are and how they work, in addtion you have to know how the garbage collector works (that's a big complexity cost right there). In the same way that c forces you to be aware of pointers the borrow checker forces you to be aware of ownership.
I'd like to introduce you to resource leaking and the Android activity/context lifecycle...
Just because you don't have to think about them doesn't mean that they aren't concepts that you should still be applying in GC'd languages. You'll just get bit by them when you least expect it or your load crosses some invisible threshold.
That said there's a valid point in that the large majority of software doesn't push the performance/memory threshold enough to make it a primary concern.
With GC'd languages you say "I don't care" to the lifecycle question which can(and will in any moderately complex software) come back to bite you.
GC doesn't guarantee you won't have resource leaks (of which memory is one). It guarantees that when you no longer reference an object it will be freed and that while you have a reference its valid. That's the lifetime problem. The Rust docs call it the same thing for that matter...
I've found that in GC'ed languages (like Python or JavaScript) not taking care of "lifetime", easily results in logic bugs. Better than crashes* - but still problematic.
* Maybe not always better either. Not crashing may lead to silent data corruption. And obvious problems (like crash) tend to be fixed faster than subtle ones, in practice.
In contrast, I find the way Go builds concurrency into the language 'complex' in Hickey's sense. Similarly the way that slices and maps are parametric, but other types are not. The implicitness in interfaces. The way capitalization determines privacy. Go seems like a very 'complex' but 'easy' language.
However, I'd even say I'm not convinced that I want my language to be simple. I want my system to be simple; its possible that by abstracting over an inherently complex domain, and by limiting my choices, a complex language can encourage me to create a simple system. I think ownership encourages the creation of simpler systems. I think a well factored system and a simple system are synonyms.
> We’ve established that when ownership is transferred to another binding, you cannot use the original binding. However, there’s a trait that changes this behavior, and it’s called Copy. We haven’t discussed traits yet, but for now, you can think of them as an annotation to a particular type that adds extra behavior.
That's two additional concepts that interplay with variable binding. Two more _implicit_ concepts at that. In contrast maps being parametric doesn't affect other things (other than some syntax which is v0v). It is an additional concept to understand yes. But it's not intertwined with the other language features.
> However, I'd even say I'm not convinced that I want my language to be simple. I want my system to be simple; its possible that by abstracting over an inherently complex domain, and by limiting my choices, a complex language can encourage me to create a simple system. I think ownership encourages the creation of simpler systems. I think a well factored system and a simple system are synonyms.
I agree with you here (especially the later part). Again I do like Go, but I'm not fanatical on this subject. Languages can _certainly_ push you to best practices. Do Rust's? It's probably pretty domain specific.
However, I don't think binding and use of a variable is 'complected' with its lifecycle. These are semantically significant concepts that I like Rust helping me manage, irrespective of their impact on machine memory.
Also, I should be clear that I do web development professionally and I think the best practices Rust encourages are absolutely applicable to that space, and general application development. That those practices are also high performance in Rust is almost incidental.
In short, I agree with you.
Go definitely performs better, buy I think it'd disingenuous to claim that Go has better tooling.
What Python does have is a far richer third party library base.
I assumed this is what you were talking about. i.e. Ipython, Jupyter, pdb, etc.
I think it's uncontroversial to say that Rust has a tougher learning curve. But once you're past that, I think the jury is still out.
That's great until you try to do some real world things with it and run into endless impedance mismatches.
Rust isn't quite so far up its own butt, however the developer community doesn't seem to realize that there's a massive gap between the toy example programs in their documentation and the level of knowledge needed to build something worthwhile.
I don't think Servo and rustc itself are "toy example programs".
I've been through the Rust Documentation (doc.rust-lang.org) and Rust By Example (rustbyexample.com) several times now, and I have to jump on IRC to get answers to (what I think should be) some pretty simple questions.
fn simplified_example<T>(a: T, b: T) -> T
where T: Copy + PartialOrd
{
if a.abs() < b.abs() {
// some irrelevant math goes here
} else {
// slightly different math goes here
}
}
I'd like to be able to call this generic function on anything that has a .abs() method. That includes i8, i16, i32, i64, f32, f64, and so on. Is that a shortcoming in documentation or an unfortunate consequence of Abs not being a trait? Several people on IRC recommended the rust-num/num crate, but that has several other deficiencies, and I don't think I should need another crate (unwanted dependency) to call .abs() on f32 or f64. With help, I came up with a workaround, but this was harder than I think it should be.The grandparent poster said:
>> the developer community doesn't seem to realize that there's a massive gap between the toy example programs in their documentation and the level of knowledge needed to build something worthwhile.
I have to agree with him. The level of Rust-specific knowledge required to get the absolute value of a number/type in a generic function is pretty significant. The example above is just a small part of a library I'm working on and only one example.
I'm clearly not an expert in Rust, and as such I realize my opinion isn't worth much, but it seems to me any attempt to build a numerical hierarchy will result in something fragile. Scheme has a well thought out minimal hierarchy, and even without all the complexity from all the fixed sizes I don't like theirs either. However, it's alright because they have dynamic typing as a release valve. C++ templates dodge the problem because they act more like duck typing, and I think I understand why Rust won't go that way.
Instead, I think you should abandon the goal of trying to create a hierarchy and just treat each small concept as a trait. Rather than creating Signed for things with a sign, and Float for things that are floating point and so on as in the num crate, create Abs as a small concept in its own. Then anything on which you can call .abs() implements the Abs trait. This includes things which aren't "signed", such as unsigned numbers and complex numbers - all of those have a useful (even if sometimes trivial) definition of .abs()
Trying to build a hierarchy reminds me of the stereotypical examples of bad object oriented design - I'm sure you know the cliches where Human derives from Ape, then Mammal, then Animal, etc... A hierarchy is like trying to build a directory tree, but numeric traits are more like labels on your gmail messages. Traits seem more like mixins or interfaces.
You'll end up with a lot of one or two method traits, but you'll also allow people to implement those traits for their own types and implement generic functions over those traits. The Float trait is a really good example of why categorization/hierarchy is unlikely to work well - That trait requires methods like .min_value() which don't make sense for complex numbers, but it is also the interface for .sqrt(), .exp(), and .ln() which have very good definitions for complex numbers. The Float trait in the num crate does not allow me to make generic functions over f32 and Complex<f64> if I want to call .exp().
> For just abs it should only be a few lines of code to make your own
Yeah, the IRC people helped me find a different workaround, but I agree that I'll just make my own trait instead. However, the .abs() methods already exist on all of the signed built in types and so I'll probably call mine .mag() on a Mag trait to avoid confusion (I'm not sure if there would be a collision/compilation error if I used the same names?). This also allows me to implement Mag for unsigned types which is useful for generic code (for examples L1 norms of vectors).
Btw, the docs for i64 have some cut-and-paste errors where they indicate i32 but almost certainly meant i64.
The examples are cut and paste errors in a sense; as it says at the top of the page, the examples are going to often use the wrong number type, because all that code is generated by macros, so for now all the number docs have to use the same type. It's a bug that needs to be fixed, but it's not a trivial one.
Yes, there are a lot of common math functions, but why is that bad? The alternatives are worse.
> The examples are cut and paste errors in a sense; as it says at the top of the page, [...]
I understand, sorry about that. I didn't see the comment at the top of the page, and I wasn't sure you knew.
Also, no worries about the bug! I much prefer over-reporting than under-reporting when it comes to these things.
I'll think about it, but honestly, that sounds super unpleasant. I've seen the bike shedding that happens in those kinds of discussions, and I think it would be difficult to convince any decision makers that there is even a problem if they haven't stumbled upon it already. I don't think many of them do numerical computing, and the language and libraries already scratch their non-numerical itches.
There's a _lot_ of ways you can go in a general purpose programming language; I don't think it's something that "nobody has hit", they just use the workaround of making their own trait(s) and it hasn't been enough of a pain to try and spearhead a change.
What sucks about programming in Rust? What makes it a bloody fucking fuck? I disagree on both these points:
* Rust enums and move semantics make writing state machines a pure joy. Far nicer than in C++ or Go.
* Pattern matching (both fallible + infallible) is very developer friendly.
* Option<T> and Result<T,E> are totally brilliant.
* It's trivial to add and version-fix a dependency on an external library with Cargo.
* Rust's ownership and move semantics help you to write cleaner, easier to understand code.TL;DR: compile time checking that your state machine transition is correct.
you cannot uncomment the comments in the main function and use it wrong, the compiler prevents you to compile this code.
these two sections from the book may help understad this
https://doc.rust-lang.org/book/ownership.html
https://doc.rust-lang.org/book/references-and-borrowing.html
if you're more into videos this may help
Steve Klabnik has a more complete write up regarding this http://words.steveklabnik.com/structure-literals-vs-construc...
let new_socket = OpenSocket { port: 12 };As it stands, Rust code can break the principle of least surprise, since what happens in a method can affect the associated instance variable (in this case `open_socket`). A user could easily write several lines of code and only discover that `open_socket` has been moved into a method at compile-time.
I found myself having very small coding-compile iterations when writing in Rust to catch issues like this (but that was also undoubtedly due to the fact I was learning).
Is there a pro tip to avoid this issue?
I'm in the middle of reading the Rust book right now, so this may be dumb, but... Isn't this exactly the point of using a naked `self` instead of the reference `&self`? If it's not a reference, `self` is consumed by the method. At least that's my current understanding
If you mean that the one-character difference is very small for such an important semantic distinction, I might agree.
As for the state machine example, I like it a lot as well. It's very similar to the idea of "making illegal states unrepresentable" from the OCaml world: http://fsharpforfunandprofit.com/posts/designing-with-types-... (This particular post is F#, but the idea is general.)
Yes it is, but you only see it's a naked self if you look into the method. From the outside there's nothing in the name to indicate that it doesn't take a reference. So potentially it means that for every method call you need to check that it takes a reference to `self` instead of ownership. This would be especially annoying with third-party libraries.
In Rust, I personally don't think about moving vs. not until the compiler tells me I have to, because it usually doesn't matter. Rust previously required moves to be explicitly marked, but it wasn't worth it in practice: http://smallcultfollowing.com/babysteps/blog/2012/10/01/move... (see "Wait, doesn’t this make it hard to know what your program does?" in particular.)
It took me longer than 4 days (almost two weeks) to just read the rust book (part time), so yes you're not going to get from 0 to 100 in just four days. If that's your benchmark, how fast you can get going, then you're right Rust isn't for you. But nobody would expect anyone to get up to speed with C++ in just four days.
Rust is a systems programming language with a focus on security and performance, not a play toy for people looking to write small scripts.
Do you need to read a really good Go book to be productive in Go? If not, I think the OP's point stands, no?
There are certain languages which are different enough from traditional semantics to need an explanation. While others are similar enough to be "obvious" if you've used a semantically similar language before.
To give a specific example, Powershell. It is semantically a very different scripting language from almost anything else in the market. Extremely well designed and powerful, but you'll want to sit down and read an actual book on how it works to get to handle on it because you won't have the mental map already to do so.
Overall this is why the programming language market is so predisposed for new languages looking and feeling like old languages. People just aren't comfortable stepping too far outside of their comfort zone semantically (even if they'll happily do so for a few new language features and libraries).
One of the best things about Rust is that it's a paradigm change, it takes time to get your mind around it but it is worth it if the language fits your use case.
I'm not sure that's fair. It seems more likely that people who choose "boring" languages do so because they have things to build, and want to pick a language with a good risk/reward value. Not out of some academic interest or comfort calculation.
On the other hand, if someone can pick up Go and produce a side project with two weekends worth of messing around, they're much more likely to be supportive of the language.
Obviously this isn't ideal, but I don't know how you'd fix the problem.
Secondly, as per the Rolling Stones, you can't always get what you want.
It's strange the short term focus that is accepted in what are nominally professionals.
That's actually a reasonable beginner's use for Rust though [0]. It's easy to do and will expose you to enough of the language to be a learning experience, but not so much that you could get badly stuck. And the benefit is that it's a lot easier to share binaries than it is to share scripts that rely on a particular interpreter being installed.
[0] http://www.chriskrycho.com/2016/using-rust-for-scripting.htm...
I do wish there was an actual Rust interpreter.
> Rust is a systems programming language with a focus on security and performance, not a play toy for people looking to write small scripts.
If you're suggesting Go is a play toy, you're wrong.
IMO this tone or slights against Go will hardly endear Rust to people. Rust's superiority should stands on its own. Not just you I have seen this sentiment many times by Rust enthusiasts on reddit and elsewhere.
Go already has successful companies like Docker, CoreOS etc literally built using Go. There are 100s of companies using Go productively. Very large software like kubernetes, docker, etcd are written in Go and they are not toy scripts.
For some people who've spent the time with it, Rust is a godsend, but that does not mean that by believing so they implicitly hate Go in anyway.
Go clearly has a lot of people who love it as much as people who love Rust, and because they overlap, there will constantly be a conversation of which language to pick for cases where it looks like both could be used. As such, the learning curve for Rust will always be an impediment, to its adoption, which is sad, b/c in all my experience it's the only language that I've used which inherently answers every core issue or fundamental bug that I've encountered in my career. This is why I'm really excited that the Rust core development team has decided to focus on development experience heavily this year.
Docker and CoreOS are not really successful companies...it's hard to say if any of the companies in that space will be successful.
While Go's frustration often comes from debugging sloppy concurrent code, the frustration with Rust is often about having your code to compile. But once it compiles, it runs beautifully. Some prefer the former, I personally prefer the latter. Also, I think the argument of increased productivity with Go should not be the main argument, because the time you save writing a large program, you will probably lose trying to debug a deadlock (which could still happen in Rust but harder to make happen) or a random data race that the race detector couldn't catch (or in a vendored library).
If I had to define the experience with both languages, Go is the instant gratification option, you feel productive quickly, the language is easy to learn with a forgiving syntax sometimes at the expense of correctness. Rust is the delayed gratification option, frustrating at times, very hard to learn and master but offering strong safety guarantees with an elegant syntax (which is a personal opinion).
Also it's a controversial topic in the Go community but the lack of generics...
Rust has its downsides too, for example the slow compile times or the lack of maintained libraries for some core functionalities but overall, programming with Rust is a much more enjoyable experience once you start getting ahead the learning curve.
One last thing: It is hopeless trying to learn Rust in four days without very good resources/books. I recommend "Programming Rust" from OReilly if you really want to dig into the language. It's incomplete at the time of writing but still one of the most useful resource out there (with the free Rust ebook). My knowledge of Rust dramatically increased after reading it and I felt much more confident writing larger programs.
This sentiment (or slight) is the same as always said of C and C++, that they are used to make real things (systems and infrastructure, foundations), and the sentiment (and slight) is there because there's a lot of truth to it.
Which language was used to write our *nixes, windows, the biggest web servers (apache, nginx, IIS), haproxy, mysql, postgresql, oracle db, etc, etc? C/C++, of course. What else?
Docker is just a front end/abstraction layer for the features already in the Linux kernel (written in C). You can write, what's essentially just automation tools, like this in any language.
I don't think what you write in this post is a testament to any strength of Go. If currently there exist a testament to its strength it must just be the fact that it's used; adoption rate/popularity, although I have not seen the numbers.
That's actually a perfect example of "systems programming", in the true sense of the phrase. And it is done in Go, because it can be done in Go.
I'd be better doing it in another language, I'm doing it mainly to keep getting familiarity with the language.
(In my case, I wanted to print out the name of the executable. Having to convert OsStr to something I can print, I understand - the filesystem encoding can be different from the console encoding. But it took longer than it should to find a path between the two.)
I never had to print an osstr yet but it seems fairly straightforward when looking at the docs? There are three obvious functions one could use, each for different use cases: https://doc.rust-lang.org/std/ffi/struct.OsString.html
os_string.to_str() // returns Option<String>
os_string.to_string_lossy() // returns Cow<String>, replacing illegal characters
os_string.into_string() // returns Result<String, OsString> - String if the data is valid, stays OsString for error handling otherwiseI always use stable compiler. Never had a need for nightly.
I don't recall anyone ever saying this, where "the one you want" means "the one that supports procedural macros".
We have had this discussion for a very long time, and it has always centered around serde and diesel. Those have been the biggest nightly users since 1.0, and probably before then.
Not really. serde and diesel have been more convenient on nightly for a long time now. It is only just now that we can say the "next stable" because that's when the particular feature they need will be stable.
> I'll note that their story is more like "the next release will be better, but don't wait for it".
I can't wait to use Go's `sort.Slice` function[1], but I'll have to wait for the next stable release.
Seriously though, I'd say the key difference between Go and Rust on this venue is that while Rust is stable, there's still more stuff we'd like to add. To that end, we encourage experimentation by making the acquisition of a nightly Rust compiler really easy. This naturally leads to people experimenting with nightly and publishing crates that either use it or require it. This is a necessary part of experimenting with new features and getting feedback.
Go's scope for experimentation is much smaller. Their changes are much more incremental and conservative. It's a really nice benefit of using Go.
My point is: this is a trade off and it should be presented as such. In Rust land, it requires that you---the user of Rust---ignore crates that require nightly or ignore features of crates that require nightly.
Very few projects require nightly features and of those that do, most of them will work on stable in just a couple of weeks!
Plus you can easily switch between versions with rustup!
Until they don't again because clearly the authors have no interest in being on a stable compiler release.
> Plus you can easily switch between versions with rustup!
I don't think people are complaining because switching versions is hard. Just unnecessary, risky, and yes kind of annoying.
This is false, most uses of nightly are because of macros, which will be stable in the next version of Rust.
Then use an older version of the package. Unless the package in question was relying on some "nightly" feature that never made it into stable, then you should be fine.
In reality, they often work perfectly fine on a reasonably-modern stable, and simply haven't updated their documentation and READMEs to indicate "oh yeah, this nightly feature is now in stable, so just use a stable that's newer than version so-and-so".
I'm not convinced the problem will just go away. Not as long as people are obsessed with the new hotness language features that require a compiler from the tuesday before last.
Cargo also makes me wary. How do I know what I'm choosing is any good, and if it is will it still be around next year. Nature of 3rd party libs I guess.
Basically, some people like having a curated library set; others do fine without and are happy to find things to use. The former set doesn't like rust as much in its current state.
I went through a similar experience in Haskell. It is worth going through until you get to the point where it is no longer a wrestling match, because then you are just as productive but any code you write is checked much more thoroughly than in a more weakly typed language.
> Fuck. This. Noise. I'm not learning a language where every project requires a separate compiler.
I guess just wait until it stabilises then.
Which is when? 1.15? 1.50? 2.0?
What was the point of that whole "1.0" thing?
This is a rhetorical question, as there's no way you've been around here so long without knowing what it is. For everyone else, however, the notion is that code written for 1.0 will continue to work for additional 1.x releases. It says nothing about iterating on not-yet-baked features--that's what nightly is for, and as they become baked they become part of 1.x releases. They're not breaking backwards compatibility; the major version remains the major version.
Very few projects require the nightly compiler and fewer still a specific version of it, but rocket is highly experimental and kind of a special case.
This is compiler version pinning. At least .toml files don't support that. Yet.
The "namespace hack" in Rust is having the "stable" namespace: a compiler that rejects all unstable code (and is forward compatible), so that people don't accidentally get pinned.
If someone wants to pin to a specific development version (of anything), there's nothing the project can do about that, other than being closed source and only publishing stable artifacts. Tooling like rustup that makes it easier to pin means that people who choose to experiment can still use stable by default (easy to switch back) and that others can easily collaborate on the experiments, both of which seem like strict positives.
All the libraries I have needed for my projects in Rust support stable. Maybe shortly after 1.0 some people were not playing by the rules yet.
The only exceptions I've found are some truly elite macro things and stuff to support code generation.
Honestly I feel like peoples biggest issue with Rust is that they're trying to write things in Rust that don't actually need Rust. They want the ease of use that you get with a garbage collected language and then get annoyed when Rust forces them to worry about allocations and cleaning up after themselves. C++ should have the same issue but its been around long enough that most of those details have safely been wrapped behind STL and other abstractions to the point where most people don't need to worry about them. Perhaps in time Rust will reach that point as well and everyone will be using the equivalent of Boost that abstracts worrying about whether something is a Box<ARC<Foo>> behind a nonthreatening layer of macros.
To your other point, it saddens me that Rust isn't well-suited to higher level applications. You need very strict performance requirements before it makes sense to pick Rust over Go (you can squeeze a lot of performance out of Go, and even optimizing Go is friendlier than writing naive Rust IMHO).
I've written a decent bit of Go, and I understand why a lot of people like it, but I don't think it's inherently better for application development.
I want to be comparatively productive in Rust, I just never seem to get there because of the huge volume of decisions (even if the decisions are mostly easy) that need to be made for even straightforward code units. I think what I really want is Rust lite, or Go with generics and abstract data types.
None of that is to make a value judgement, just an observation; but I would still argue that the language can be used in a highly productive manner. Nonetheless, it's perfectly reasonable to argue it's not worth adapting to it, especially if Go is already solving your problems and keeping you productive. I switched because Go's design doesn't seem to fit me very well, and I continually tripped on issues that I don't encounter in Rust. (So yes, perhaps I'm quite odd.)
I do understand the want for a Rust-lite; the thought has crossed my mind more than once. So far, Swift seems closest in many ways and may get close once the Linux/cross-platform story starts looking good.
I'm not saying Rust is easy, but I have also a long Scala and some Haskell background which made it easier to learn Rust. For me the hardest part was that I've never really wrote any proper C++ or C, although I could read them quite good. Refereneces and RAII caused some gray hairs, the type system not that much.
Well this is true for all languages! You need to think hard for every decision you take with languages like Ruby or Python. Things like how will this look like after a year, will the junior developer be able to extend it, will there be data races and so on.
Rust just enforces you to do this. And it makes life much easier when you don't need to worry about some silly mistakes you made, because the compiler will catch them.
That is unfortunately not true enough.
Rust has a brilliant solution for some really burdensome issues that C++ developers have. A lot of C++ code tends to make assumptions about ownership and lifetimes that are not formally spelled out anywhere in the code. It's all over the place, it increases cognitive load, it makes the code brittle and less modular than it could be. Rust fixes that without negatively affecting performance whereas all C++ workarounds like shared_ptr and defensive copying are incomplete and/or have a negative performance impact.
But Rust also insists on enforcing its ownership rules between variables within the local scope where it doesn't help much and feels needlessly restrictive and convoluted. I can't say I know Rust well enough to tell whether working under these restrictions can become second nature and stop being a burden.
Also, users of garbage collected languages don't have many of the ownership and lifetime issues that C++ developers are forced to think about. Especially where objects are immutable, ownership is not a concern at all.
So I think Rust is an answer to one question: How can I have detailed control over resources like in C++ without inheriting all the hazards of C++ as well.
No, Go does not have that distinction. This is even in the Go FAQ: https://golang.org/doc/faq#stack_or_heap
> When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors.
As you can see, Go uses both the stack and the heap; however, unlike Rust, it doesn't require users to know where their variables live to write correct programs.
Once you take the address and escape analysis is performed, you've got no way to easily reason about what's on the stack vs. what's on the heap, without delving into the gory implementation details which may be changed in future versions.
Since Go isn't C++ and doesn't have a huge and complicated standard that guarantees compiler optimizations, you can't really reason about what's going on.
This is essentially the same situation as Java, although the Hotspot JVM's escape analysis is probably less 'basic'.
This is not to say that Rust is better than Go in every way, but performance is far from the only facet when I compare the two.
What do you base this belief on? I'm very comfortable with C and assembly. I am very certain I understand the difference between stack and heap allocated data, but I've found lots of other hurdles in learning Rust.
One difficult aspect of Rust relative to Go must surely be generics and trait bounds. This is something it shares with Haskell and Scala. For people coming from those languages (like myself) the stack/heap thing is the most salient.
> One difficult aspect of Rust relative to Go must surely be generics and trait bounds.
Conceptually this isn't bad at all. I understand the value and mechanism of specifying the required traits for types to a generic function. However, I just posted an example elsewhere in this discussion where there simply isn't a trait for the method I want to call. That was certainly a hurdle for me, and the workaround wasn't satisfying.
I mostly understand the semantics of borrowing mutable vs read-only references, and I'm pretty sure I understand the purpose and value of the concept. However, specifying lifetimes is completely different than any other language I've ever used, and I've used a couple dozen languages over several decades in my career. I haven't gotten past that hurdle yet.
> the stack/heap thing is the most salient.
I'll stick with my statement that I understand the stack and heap well enough (from a C or C++ point of view), but composing Box, Cell, Ref, RefCell, and friends is a non-trivial hurdle.
Depending on your background there probably are various hurdles to overcome in learning Rust, I'm not discounting that, but the most uniquely Rust one is the stack vs. heap issue, particularly since that combined with issues around shared state are the sources of most of the headaches you'll run into when fighting with the borrow checker. Pretty much every other feature of Rust is similar enough to another language that if you have experience elsewhere you won't find it particular troublesome. References are easy to understand if you know C/C++ (and to a lesser extent C#). The type system is easy to understand if you know Haskell or Scala (and to a lesser extent JavaScript/Python). The generics system is pretty much the same as Java, C#, Haskell, Scala, or C++ (and probably others).
Rust IS a complex language, but it also provides a unique feature set and has some fairly unique goals. IF you need or desire the features Rust provides, pretty much ONLY Rust is going to get you what you want. C, C++, or Assembly potentially can deliver the same functionality, although it's questionable whether their ergonomics will be better or worse than Rusts for a given task, and they almost certainly will be more bug and error prone. Rust lets you have all the fine grained performance tweaking (or just plain low level access) you can get with C/C++/Assembly, while also giving a strong peace of mind that your code is correct and error free. It's also arguably easier than C++ although that comparison is a bit Apples and Oranges, C++ has a great many years of tweaking and experimenting behind it so things like Boost go a long way towards providing something like a most optimal (from a ergonomics standpoint) C++ experience that ignores all the languages pitfalls and sandtraps. Rust on the other hand has just barely hit 1.0 and the community hasn't even discussed, let alone come to any kind of consensus on the best way to tackle various issues yet. Rust will get there in time, but it's still early in the process and it shows.
Bottom line, if you need the features Rust provides, but aren't comfortable with how young the language is and want more hand holding, C++ (or maybe C) is probably the language you want. This isn't to say Rust is a bad language or you shouldn't use it, I think learning and using a new language is reason enough and the certainty Rusts type system and borrow checker provide is a very strong argument in its favor, but you need to be aware of what you're signing up for when you make that decision.
I can only speak for myself, and I politely disagree. So far, explicit lifetimes (both the syntax and the concepts) seem like the most uniquely Rust issue.
> [if you] want more hand holding, C++ (or maybe C) is probably the language you want.
Heh, if you think C++ holds your hand, I'm not sure we're on the same page. What I know of C++ was hard-won after reading many books and fighting with the compiler for many years.
Also, C/C++ comes with the venerable "kill the process GC" that makes it easy for beginners and other mortals to feel good about code even when it not strictly is good. Rust is all about making that kind of quality mandatory.
This is why I think the best characterization of Rust is "an Anti-Sloppy Programming Language".
My understanding is that the rust team has put 2 priorities ahead of everything else: zero cost abstraction and safety. Given those constraints they've done a fantastic job in the area of ergonomics, but I don't know if it will ever be easy. Maybe rust is as easy to use as it can be.
> HashMap<T: Hash+Eq, u64>
I can take a wild guess that T is a generic type defined on either the method, struct or impl in which this HashMap is declared. It's on the method/struct/impl. You don't necessarily need the type bound on the struct, but you do on the impl declaration:
struct MyStruct<T: Hash+Eq> { map: HashMap<T, u64> }
impl<T: Hash+Eq> MyStruct<T> { pub fn insert_something(&mut self, my_key: T, my_int: u64) { self.map.insert(my_key, my_int); } }
try it here: https://is.gd/RwUpQx
Now that developers are ready for new languages, D is already an "old, failed" language.
It reminds me of selling a house. If you put your house during a hot market and go into contract right away, but then your buyer can't come up with the money and it falls out of contract three weeks later, bringing your house back on the market it's now no longer "newly available!", and everyone thinks that, because it didn't sell in the first week, there must be something wrong with it.
My personal entirely uninformed opinion of D is exactly that: It's an older, "failed" language. It's not on the upswing in popularity. It didn't catch peoples' hearts and minds. And if only because of network effects, therefore it's not worth learning, simply because no one else is learning it, and so therefore its ecosystem isn't expanding exponentially.
Success of technologies is far less merit than one might expect. See: VHS vs. Beta.
During the last year the D foundation was founded and is spinning up operations. The 2016 D conference had a visitor record again with over 100 people. Downloads are growing over the years [0]. The package manager dub is now fully integrated. You can find success stories on the blog [1] and companies using it [2]. There are books.
[0] http://erdani.com/d/downloads.daily.png [1] https://dlang.org/blog/ [2] https://dlang.org/orgs-using-d.html
The reference compiler backend is still only free as in beer, so there seems to be no way to include "DMD" in Debian. However, LDC (reference frontend + LLVM backend) is practically in sync with DMD today.
The advantage of DMD is faster compilation. LDC gives you faster programs. So, for development I prefer DMD for the faster iteration.
The backend comes from the Digital Mars C++ compiler, which is/was commercial. That is probably the source of the confusion.
the syntax seemed extremely similar, as if they took c++ and just tweaked it a bit, rather than working at least somewhat from first principles.
seemed almost comically pointless at the time. no doubt it's evolved since then.
Learning D and Rust requires an effort Go proponents are not capable of.
Another problem is that both D and Rust try to replace C++ and C respectively. But C++ and C developers would never switch to anything else as they feel comfortable using the later, even despite all the gotcha of manual memory management.
Most people using Go today come from Python,Ruby,PHP,JS and co, not from C or C++ or Java. They want performances, without paying a price for "complexity". D is fine today, but it lacks a community behind it and a good ecosystem for web development.
If Rust had garbage collection, it would certainly be more popular but it's not going to replace C anyway. We are stuck with C and C++, they are not going away.
I understand why Rust is what it is, I also understand that most developers don't want to deal with explicit move semantics and lifetime management. Rust isn't going to seduce Ruby,Python,PHP or Javascript developers. the later only want better performances and think Go is good enough. They don't care about explicit memory management.
I'd like to see a version of Rust with garbage collection. The language itself is good.
Why? What is the added value?
I could be off base. I haven't programmed in Rust. I've just skimmed through the documentation and little code projects written in it every once and a while.
More importantly, it could be very useful to interact with code in garbage collected languages while having Rust model of the behavior of the garbage collector using its type system.
There are many people who considered it and rejected, or even moved to other languages. C++ developers with existing large codebases may never switch to anything else, but that doesn't mean that the rest of the world is stuck. People are writing databases, browsers, operating systems and device drivers in rust right now and eventually some of those will become successful project defining state of the art.
> I'd like to see a version of Rust with garbage collection. The language itself is good.
There are myriads of languages with GC. There are very few modern ones without it.
TLDR: Rust did the hard work of convincing others to try it out and evangelize the language, and as a result, it has become popular and has grown a strong community.
I think your logic is not sound . If they wanted to use python2 (only with their own runtime which does not have GIL) instead of switching to go (eventually) they would write a runtime instead of transpiler ?
Please correct me, I am not 100% sure.
I've tried to get into Rust on/off and each time I'm left feeling like I needed a semester of instruction just to write a trivial program. Each attempt to go back to it, I would immediately hit a wall.
Go, on the other hand, felt quick to pick up. While I don't use it a lot, it's focus on a few composition patterns, and really sticking by that, made it easier for me to learn and come back to when I go back to my Python-centric existence.
Rust on the other hand, I haven't thought about nor touched, outside seeing an HN headline.
I would phrase it like this:
• Go is a useful, safe subset of C (with a rather expanded libc.) Go lets you do fewer things than C (e.g. hard real-time things; processor-optimized things) but for most people, they don't need those things, and delving into them was what seeded the bugs into their C/C++ projects in the first place, completely unnecessarily.
• Rust is a formalization/codification of idiomatic C++. It's not a subset; it's a rethink, like Plan 9 is a rethink of Unix. You can do everything in Rust that you can do in C++, and the compiler will catch (nearly) all the bugs you would have left in the equivalent C++ project.
In other words, Go says "you don't need this"; Rust says "if you're so sure you need this, let's ensure you do it right."
For any given piece of code, either Go's assertion or Rust's assertion could be "correct." You either need the things only Rust lets you do, or you don't.
For a large codebase, though, I expect the answer is actually nearly always going to end up somewhere between. Big apps or libraries always have that one part that does heavy lifting and has to go fast, but go fast correctly; and 99% that doesn't.
Thus, I envision the future of many "systems software" codebases to look like this:
1. (1% of LOC) an "engine" library, written in Rust, which compiles to a C-ABI shared object;
2. (3% of LOC) an "engine wrapper" library, written in Go, which imports the "engine" object using Go's C FFI and then exposes a safe Go API to it—and also, the tests for the engine library, written in Go because it's easier;
3. (96% of LOC) the app itself, written in Go, which calls into the "engine wrapper."
Or, consider Firefox. Firefox's rendering engine will soon be Servo (Rust). I presume that its networking and its Javascript engine will eventually go the same way. Eventually, the only stuff left written in C in the Firefox codebase will be "glue code": business-policy logic that doesn't do heavy lifting, and doesn't need speed. In other words, the exact type of code where Go's "you'll be safer without this" assertion applies. Firefox as Go + Rust: why not?
Idiomatic C++ favors values and copy semantics. Rust favors references and move semantics. C++ has function and method overloading, and Rust does not. There are so many places where Rust chose exactly the opposite of "modern" C++ that calling Rust a codification or formalization of C++ makes no sense at all to me. Rust has some great features, and C++ has a lot of warts, but Rust is definitely not an improved C++.
Rust seems like a reaction to problems in C++, but for many design decisions I think they erred on the side of doing the opposite of C++ and ignored the things C++ does well.
What are you looking for that Rust is missing?
Here's an example that has bitten me several times now. Try and implement the following C++14 code in Rust:
template<class T>
struct Something { T a, b; };
template<class S, class T>
auto operator *(const S& x, const Something<T>& y) {
return Something<decltype(S() * T())> {
x*y.a, x*y.b
};
}
There are ugly workarounds, and I'm certain they have their reasons, but it's so much simpler and cleaner in C++.This is a bold claim, IMO somehow misleading. To "do everything you can do in C++", you have to use the unsafe part of rust, where you essentially lose of the compiler guaranty. Second and maybe ever more importantly, rust never promise the remedy to "nearly all bugs", the universe of bug preventable is a well-defined limited set.
Go isn't a replacement for C; you can't use it for many forms of low-level systems programming, because it has a runtime. Go provides a high-performance replacement for things you might otherwise write in Python or similar, and in that capacity it works quite well.
Rust can do anything C can; its capabilities build upwards from bare metal, not downwards from higher-level languages. It intentionally doesn't have a runtime, and its approach to avoiding garbage collection (namely borrow checking) provides safe memory handling without a runtime at the expense of being a novel language feature without analogue in other languages. Much of the learning curve in Rust comes from that.
Oh, absolutely; you can certainly write higher-level code in Rust, and it makes for a far more comfortable experience than writing higher-level code in C. I didn't intend to suggest otherwise. And the ongoing futures work helps Rust address several additional areas that Go does well.
However, I don't think it makes sense to group Go and C together, syntax aside. Go cannot replace every use of C, because of its runtime and related expectations. (You can push it into some low-level environments if you make accommodations for its runtime, but you can do the same for languages like Python. You wouldn't necessarily want to do so in production, though.)
I only play around with it for personal projects but I would have thought that if I went from scratch and spent four days on it like the article stated that I'd be pretty productive.
It's too bad that you feel that way but lots of people like programming in Rust and don't like programming in Go. I have never before had to pay so much -- in terms of type annoyances, packaging workflow and boilerplate -- for so little.
It's not like that, it's not hard to need geniuses, it's just too different for most people. And different takes time to learn. Which is definitely a design flaw for a language aiming for some adoption. I don't think they can improve it at this point, but they can provide a lot of value where other languages are weak.
The Go comments sound a lot like Java in the early days. Easy language. Quick to get started. Easy to get lots of programmers. If desperate, just train them. Today Java is not so simple anymore, because the Pros wanted more features (eg Generics). I believe Rust will get Generics at some point. Typesafe containers are too useful.
I certainly hope the Go authors can find an implementation of generics with a set of tradeoffs that appeal to them. Typesafe containers certainly are extremely useful.
https://news.ycombinator.com/item?id=9622417
I agree, the value in generics/templates is in not cutting and pasting an algorithm or container to handle multiple types safely. A true macro system (and Rust's is pretty good one to model after) also solves this problem, and I wonder if Go would ever consider adding such a thing. It seems like a safer approach.Also, there is the hybrid method between C++ and Java, which C# implements: Instantiation for value types and type erasure for reference types.
Heh, I certainly can't speak for them, and I don't know what they've considered outside of that link. I also don't know C# or OCaml well enough to say if Go should/could adopt their strategies. However, Java's generics aren't very satisfying, and I think Go is wise to not jump in prematurely.
Hygienic macros (as in Scheme or Rust (Nim? Elixir? Julia?)) seem like a more mature concept that could solve most of the problem.
This is an interesting thing I realized after I read the postmartem report of why "rethinkdb failed".
Users want something they can get and build apps with quickly, rather than something feature perfect four years after they want to build apps.
Go essentially answered the question, "I want to write a webapp, don't want to use Python or RoR, what do I use?". It is because of the easy to understand syntax and the powerful stdlib they were able to answer that question.
The language is amazing. I don't have any idea about Rust, so won't comment on that.
Isn't this also because it's actually quite a bit different from other languages? I think how difficult something is to learn is largely dependent on how similar it is to something you already know.
I can see how from a business standpoint, being able to start coding is great, but I also feel a bit sad when I see people not really giving a language a real chance, especially when a week or two may not be all that significant in the long run, whereas choosing a technology is potentially pretty significant long term.
Rust has the same problem as C++ (to a lesser degree; maybe it'd be more fair to compare it to Java), in that if you're a casual coder who dips into a bunch of different projects in a bunch of different languages without ever really specializing, it can be overwhelming. Go is much more readily accessible; partly because it is less ambitious, and partly due to philosophical differences. I can read most Go code, today, having only worked through "A Tour of Go" on the website; I can't even begin to read Rust code after similar time/effort invested.
So, yeah, Rust really is hard. It may be necessary complexity to solve the problems they wish to solve. But, it means I'm unlikely to ever have the time to invest to make it a part of my toolbox, unless it becomes my primary job; whereas I'm already almost there with Go. I'll probably build something real in Go within the next month or two. It can be something I do for fun, in my spare time, and I can expect to get useful results.
I want to like Rust. I'm just finding it hard to get to know Rust.
For me I feel like it took time but I've formed a good intuition and understand of the rules, but I would have a very difficult time formalizing a description.
-- every single developerI'll quote some of the proposed vision statement here:
# Rust should have a lower learning curve
Rust unique features make it valuable, but also means there's a lot to learn.
A common refrain is "the first couple of weeks are tough, but it's oh so
worth it." How many people are bouncing off of Rust before they get through
those first couple of weeks? How many team leads are reluctant to introduce
Rust because of the training needed? 1 in 4 survey respondents mentioned the
learning curve.
Some potential avenues: improved docs/training materials; improved compiler
errors; language improvements (e.g. things like non-lexical lifetimes).
# Rust should have basic IDE support
For many people—even whole organizations—IDEs are an essential part of the
programming workflow. In the survey, 1 in 4 respondents mentioned requiring
IDE support before using Rust seriously. Tools like Racer and the IntelliJ
Rust plugin have made great progress this year, but compiler integration has
yet to get off the ground, and there's a lot of room to do more.
# Rust's community should provide mentoring at all levels
The Rust community is awesome, in large part because of how welcoming it is.
But we could do a lot more to help grow people into roles in the project,
including pulling together important work items at all level of expertise to
direct people to, providing mentoring, and having a clearer on-ramp to the
various official Rust teams. Outreach and mentoring is also one of the best
avenues for increasing diversity in the project, which, as the survey
demonstrates, has a lot of room for improvement.
[1]: https://internals.rust-lang.org/t/setting-our-vision-for-the...This was an issue as I learned. With widely popular languages, googling a simple question like "reverse a list javascript" or "sum an array php" comes up with a hundred different suggestions and discussions.
With Rust, most of the discussion is very low level, often by people working on Rust itself and usually the rare answer you find is terribly out of date.
Just having a more stable language so answers don't become obsolete is a big step, but it will still take time for those very basic questions to get answered.
let mut v = [1, 2, 3];
v.reverse();
assert!(v == [3, 2, 1]);
let a = [1, 2, 3];
let sum: i32 = a.iter().sum();
assert_eq!(sum, 6);
I'm not saying everything is simple, and there are hard parts of rust to learn. But as far as solving simple problems, I haven't ever had an easier time then with Rust.No, they were just off the top of my head, not to be taken literally.
And the docs aren't organized as a tutorial. If I don't know what a method or type does, or even that it exists, I'm not going to know to look there for examples.
I can totally relate to this: I spent almost a year reading blog posts about Rust or comments on /r/rust, between the first time I read the Rust book and the time I fell confident enough to write m'y first Rust software (a cli tool).
In the meantime I learned Go in three days and I was able to work with it.
Why did I continued to learn Rust your may ask ?
Comming from web languages, learning Rust was a pilgrimage. I learned so much about low level computer sciences: what is memory locality, what's a data race, how does a memory allocator work, what is inlining, etc. It was definetly worth the time I spend on it.
And now that I'm proefficient with both Rust and Go, I can tell I prefer Rust by a large margin. Rust is a really well designed language, with a really nice community and a decision process designed to improve the language with feature real people need. Whereas Go is a monolyth controlled by its creators built around the dogma of symplicity at all cost, and they usually reject users proposal for improvements.
Besides the politics, Rust is way more expressive and going back to Go is always boring because of the boilerplate. It's also way easier to shoot yourself in the foot in Go than in Rust, by ignoring error, misusing a shared variable (forgetting to use a lock, or using it improperly) or dereferencing a null pointer.
In short IMHO Rust is more difficult to learn than Go, but you learn more and when mastered, the language is more helpful.
To all of you strugling learning Rust: carry on, it's worth it ! And also, don't wait and go on #rust-beginners [1] on IRC[2], it's really helpful.
[1] from a web browser: https://client00.chat.mibbit.com/?server=irc.mozilla.org%3A%...
[2] irc.mozilla.org
"Latency and soft-realtime performance
"Again, zero-overhead abstractions and no stop-the-world GC pauses give Rust a clear +1.
"It is worth noting that Go came nearest disqualifying itself entirely here. If it were not possible to lock out Go’s GC in critical regions Rust would win by default. If NTP’s challenges tilted even a little more towards hard real time, or its critical regions were less confined, Rust would also win by default."
As an aside, if ESR couldn't get Rust's ownership model, I wonder how long it took him to internalize how not to write memory bugs in C?
In C++ I loathe generic programming, a template instantiation error is something I fear. Whereas in Rust I knowingly compile broken generic code all the time, because the errors are (usually) good enough to help me fix it.
In a sense yes, although Go compiles to native code and offers completely different performance compared to an interpreted language like Python, and no JIT warmup periods of JIT compiled languages, and no dependencies on runtime environments or virtual machines, and from the outside runs confusingly similar to a native C application.
Also, C/C++ is such huge languages that they are in use in _far_ more scenarios than low level systems programming. Maybe that was the intention when they were designed, but usage exploded on e.g. Windows before the days of .NET and many still prefer it over .NET due to performance. Even if these are then desktop applications using Win32, Qt, or even MFC. C is also heavily used in development of e.g. GNOME applications with GTK+. Also a huge field of non-system level desktop applications. C / C++ is also usually preferred for games development.
This is why I think there's room for Go even in the context as a C/C++ successor simply because of the field where C/C++ is used is just so darned huge, although I do agree that if the application here is a close-to-metal application where you don't have time to wait more than a millisecond on a garbage collector or need complete control over memory use, Rust may be more useful and in this sense yes, be more like a spiritual successor to C as well.
Rust's type system, like any type system, is limited. It enforces a certain style of safe programming and will reject other styles of safe programming.
The compiler is also not great at explaining why something is wrong. I'm not knocking the compiler writers, because it's a hard problem and they're putting serious effort into it. But from ESR's perspective, he's writing code that is safe (according to a style he's developed over decades of programming) only to have it rejected with an explanation that doesn't make sense. It's frustrating.
There are ways to improve the learning experience. You can improve documentation, improve compiler error messages, and improve the type system itself (e.g. non-lexical lifetimes). The Rust team appears to be taking all of these seriously.
I think the people who make it past the initial frustration are those who believe that it will be worth it. I'm a huge fan of type safety guarantees. I've also followed the development of Rust enough to know that the people making decisions are good at designing languages. So when I'm frustrated, I remind myself that this is probably all worth it.
But reading ESR's write-up, he's clearly not coming from the same place. He believes that Go and Rust both have "strong type systems", which is clearly false. He complains about Rust exposing an unsafe construct like mutexes without realizing that Rust mutexes are much safer than in C. But to be fair, he's also thinking about a specific project that needs an efficient epoll-style mechanism and Rust is just not there quite yet.
1. Rust never "course corrected" to implement CSP, and as far as I can tell ESR is making that up.
Rust actually implemented channels and tasks as builtins first, with no mutexes or shared state available--those came later. At some point we saw the opportunity to reduce the complexity of the language and move the language closer to the metal by moving channels and threads to the library instead of keeping them as primitives. Mutexes and reader-writer locks followed a year or two into the language's development--in fact, they were partially implemented on top of channels at first. In fact, the motivation was essentially "hey, look at this neat thing I can do with borrowing: safe mutexes!", not "let's get rid of channels".
2. The idea that channels are going away is also untrue. I've heard a couple of people wish that channels could be removed the standard library and moved to the nursery since they're a fairly complex implementation, and embedded users might want a slim libstd. But nobody has ever floated the idea of channels just vanishing outright in favor of shared state and mutexes: it's absurd. Moreover, the standard library is stable and must be supported going forward, so we can't make that change even if we wanted to.
3. Go has mutexes as well. They're right in the library in the "sync" package. They're fairly idiomatic too: embedding a mutex in your structure as an anonymous field so that your structure can inherit the Lock() method is common.
4. ESR needs to be clear about exactly what defects can arise with mutexes. If he were to enumerate them, he'd find that Rust's type system is carefully designed to avoid them.
Easy vs Hard as in: - Easy to learn - Familiar looking - I know most of it already vs - Hard to learn - Nothing works as I'm used too - It's all new to me
Simple vs Complex: - Its parts are self-contained - The parts don't depend on each other - Quick to get something done once you understand it vs - Can't separate the parts from the whole - Each part depends on other parts - Slow to get something done even if you understand it
Most programmers and businesses value Easy vs Hard, because they think about themselves, not delivery of great quality software. If you focused on delivery of great quality software, you'd conclude hopefully that easy vs hard doesn't matter as much as simple vs complex.
Having said that, I think Go is easy, and for the most part, it is also pretty simple. While Rust I'd say is hard, but also very simple. Its simple in Rust to make correct software, but not easy. C is easy, but complex. C++ is kinda hard, and also complex.
So both Go and Rust are great improvements. Go chose to still be easy, at the detriment of how far it could innovate, but it still innovates. This approach is pretty smart, because what will happen probably is that a lot of people will move to Go, as it is not a very hard change from what they know, and then they will move to Go + 1, and then Go + 2, and then Go + 2, and eventually they'll end up at Rust, or something similar. Doing it that way is easier.
There's lots of this in Rust, especially in the form of premature specialization. If you define a struct or a method in Rust, you might be forced to make some decisions about lifetime, static vs dynamic dispatch, memory representation, mutability, etc.
These things are tied together deeply in order to create a tractable space in which the type solver may operate. Granted, this may also create a tractable space in which for you to think about these same issues, but that doesn't mean its any less complected/complex.
Although in the context of the simple vs easy notion, I suggest you grep this page for "inherent complexity":
https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
He talks about the inherent complexity in resource contention.
I will point out one choice quote:
"It's making things complex because that's a decision that needs to be made by someone who has better information."
Rust addresses this by forcing you to highly parameterize code statically, which is probably the least bad solution we have right now for this sort of stuff in a low-level language.
I'd agree that to me that's just inherent complexity. Let me explain.
Imagine the safest language ever, literally, by design, it prevents all bugs, except for functional ones.
Now implementing anything to the extent that there's 0% bugs is inherently complex. It might be so complex that possibly no program ever written even achieves this, in any language.
That hypothetical language would not be able to make that complexity disappear, because it's part of the problem space, and not the specific design of the language.
In most languages, we avoid this complexity by ignoring it. We allow buggy software. We generally say, as long as the bugs that exist either don't occur in practice, or don't impact users that much, it's good enough.
But, let's go back to Rich's talk. Why was he talking about Simple vs Complex? Because complex code leads to bugs. If your language's complexity leads to bug free software, the argument would change. In this case, just work harder to understand the complexity and get it done. You're then guaranteed safety.
Rust is by no means this language, but it's the closest we have currently in that space. Because of that goal, it's tackling quite a lot of complexity. Maybe some of it is accidental, and Rust2 could improve on that.
Having said this, sometimes it's fine to allow bugs, if it allows productivity. Use Clojure in those cases :p
No pain, no gain, but sure.
> Translation distance
62K lines sounds like typical C with no code reuse to speak of, A clean-slate rewrite I'd hope would be much smaller, though admittedly talking about corrode and c2go speaks of the other direction.
> Concurrency
No mention of Rust being data-race-free and Go not; unacceptably lax.
> > While I give the Rust designers credit for course-correcting by including CSP, Go got this right the first time and their result is better integrated into the core language.
Wat. Rust was originally all about CSP too, but they went away from that because there was no need to stay. You can still do all the channel stuff you want but there's little benefit force that idiom. Go's special support for this is like....special-cased generics right?
> epoll
Mio? (sure future-rs is not yet ripe, but mio alone is last I checked.) Overall, ESR seems sprinkle in lot of hatred of dependencies (bloated standard libraries are such a crutch around not having a good ecosystem), which I personally find laughable as the brightest future of FOSS is large-scale code and abstraction reuse as Haskell, Rust, and the like, are just making mainstream.
Um... you can derive the amount of code reuse in a project given no other information than the number of lines of code?
If your abstraction has a real-world vs purely mathematical / programmatic purpose, it probably sucks.
Functor > UserLoginFactoryBean.
Seems pretty similar to me.
See this very very old HN thread:
https://news.ycombinator.com/item?id=3565703
(Hopefully the standard library has improved since then.)
(Early versions of Go had a "network channel" concept, but that was removed from the language: https://softwareengineering.stackexchange.com/questions/1540...)
It just happens to offer a similar model (and have a select operation).
Edit: Does "But that's true of Golang as well" refer to the one thread/socket model or not having epoll? I may have misinterpreted what you were saying...
(I know it's doable; I did it for a portscanner, where I needed fine grained timer control. But I had to forego the Go networking stack to do it.)
I have production systems which run several 100k goroutines at once with very little overhead.
Thread-per-connection is the simplest way to do concurrent networking in Rust using only libstd, but it's not the way that most Rust programmers would actually use (at least, not for a production server handling a large number of connections).
Instead, we're layering platform-specific code in external libraries. At the raw C-level bindings, we have libc [2] and winapi [3]. For higher level APIs, we've got Mio [4] which abstracts the system APIs, and now Tokio [5], which uses futures to simplify async operations.
We're just working our way up the stack. All of these are being developed by members of the various Rust teams, so they're as "official" as everything else we're doing.
[1]: https://doc.rust-lang.org/std/net/ [2]: https://crates.io/crates/libc [3]: https://crates.io/crates/winapi [4]: https://github.com/carllerche/mio [5]: https://tokio.rs/
Goroutines are _very_ light (8KB stack size)
2kb, actually.If a goroutine calls into C, and the C code overflows or otherwise writes ‼Fun‼ onto the stack¹ … can it clobber an adjacent stack of another goroutine or other memory?
This is also true about OS threads!
I'd definitely agree that at this point Go is better for network services, and the learning curve / documentation / infrastructure of Rust is a bit scattered. I believe that as Rust matures it'll be better for the non-server stuff (no GC, etc.) and eventually become competitive with Go in this space. I'm quite fond of both languages, though!
Rust started out as the new generation engine of an open source browser, conceding that no existing language met its needs, and has been community driven since that point, often making breaking changes in pursuit of the ideal.
Go has indeed been very open to community input, but of the two, it seems clear that Rust is the bazaar, and Go is the cathedral, no? I'd love to hear how you arrived at a conclusion to the contrary.
https://github.com/rust-lang/rust-packaging/pull/49/files
But I don't see this corporate history as at all indicative. By comparison, Unix was originally developed by Ma Bell whereas Lisp was developed by a community. My view is based more on the resulting languages rather than the process that got them there.
Again, though, this is from the out outside.
(As a disclaimer, I'm a biased ex-intern.)
I would also note that Rust's development model is a lot more open, Go doesn't even accept pull requests.
(I don't disagree with the premise though, Go does seem to be less community-driven than Rust; e.g. Rust has 3 mozilla employees in the top 10 contributors for the most recent release[0], where as Go has 9 Googlers in its top 10[1].)
[0]: https://github.com/rust-lang/rust/graphs/contributors?from=2...
Copyright YYYY The Go Authors.
ever since its public release in 2009. Google only holds the copyrights for the subset of contributions to Go made by Google employees, but there's no Google credit in the sense you're describing....whereas Rust optimizes for the property "Once it compiles, it's probably going to work flawlessly on the first try."
You do spend a lot of time having a conversation with the compiler first. But I like that mode of working; even in Ruby, I have a long "conversation" with my unit tests before I ever run anything.
Rust very much lets you write working and less buggy code at first run, if perhaps not the first compile.
When I've worked with Rust, 90% of the time I found it just as easy as working with something like JavaScript or Python. However 10% of the time it becomes much much much more difficult. And those times I often end up going in the wrong direction, reading documentation and trying to apply a solution which doesn't fit (i.e. no syntax that will make things work because I am fundamentally doing the wrong thing).
For example, the other day I tried solving a problem in which I had to do an in-place matrix transposition on some slices and I was getting a bunch of compile-time errors for everything I tried. I googled later on and found this [0], which makes it seem like I would have needed to use `unsafe`, however maybe that's just a misunderstanding and there is a safe way.
People are making it out to be harder than it actually is. The semantics aided by the compile-time errors make most problems quick to solve. It's very expressive and gives types which help to avoid fragile code (e.g. `Result` and `Option`). The only times that it becomes difficult are when you need to reach for a solution but do not know what to search for, and I think this could be improved if some of the errors linked to related topics, and once the IDE support improves.
[0] https://athemathmo.github.io/2016/08/29/inplace-transpose.ht...
I have an idea as to why the author approached it this way: typically Rust wouldn't let you take out a mutable borrow to a vector while you're iterating over it. If Rust did allow that and you modified the collection then the iterator might reference freed memory!
A common way around this is to just use a typical `for <range>` loop and index into the array directly to limit the scope of the mutable borrow to _a single iteration of the loop_ and not _the lifetime of the iterator._ However indexing in Rust incurs a speed penalty for bounds checking, and the unsafe code here lets you skip those checks.
Really all the unsafe code is doing is saying: "I promise to initialize `rowcol` entries, and I won't try to access anything outside `rowcol`. Please don't burn cycles worrying about it Friend Compiler."
It looks to me as though this could've been written as a vector initialized with all zeroes (which possibly isn't even that expensive, since modern OSes heavily optimize the case of giving you a zeroed page.) Then in the hot loop the author could've just used iterators to avoid the bounds check. Using an iterator for that should be fine in this instance since you're immutably iterating over one matrix, and the only mutation happening is in the new transposed matrix.
That all being said: transposing the matrix in-place would run into exactly that scenario. now you could still do it in safe rust, you would only need to add the unsafe code if you wanted to turn off the bounds checking for maximal performance.
TL;DR: we have some concerns. :P
I agree 100% with this scoring. I'm not sure though why he weighted the 3 pros of Go as more important for NTPsec since the goal of the project are: "Our goal is to deliver code that can be used with confidence in deployments with the most stringent security, availability, and assurance requirements." Given this goal I would personally say that safety, security, deployment ease and performance are critical, which given his pros and cons would make Rust a better candidate.
What I do think is a fair point is Rust's learning curve. With that in mind I would like to suggest a third alternative: Nim.
Support for epoll/kqueue/IOCP has been in Nim's standard library for a while now and is already very mature. The learning curve is probably somewhere between Rust and Go. In addition Nim also offers a GC that is predictable and controllable.
I wonder if the ease of development already outweighs the efforts required to keep up with the language growth pains and writing wrappers for system libraries where needed.
The current maturity and future stability. Tokio's own page says, as of a week ago says:
"Today we are publishing the preliminary version of the Tokio stack, 0.1!"
"Corrode" needs to get a lot better before it is useful. Look at the Rust it generates.[1] It transliterates C into unsafe Rust. That's not too helpful. Doing such a translation well is a hard problem. The translator will need to figure out exactly what needs to be mutable where, which means building a graph of the program across function boundaries.
[1] https://www.reddit.com/r/rust/comments/4rv0uh/early_stage_c_...
The first step would be to make everything const that can possibly be const. Const stuff can be borrowed non-mutably. Then make all variables have as narrow a scope as possible, even if this means putting in more bracketed blocks. Variables which are not initialized at declaration should be combined with their first assignment whenever possible.
Then you have to analyze what can have single ownership and what can't. Non-single-ownership data has to be refcounted. That makes the borrow checker happy, but may result in excessive refcounting if the analyzer can't track usage through complex code.
Pointers need to be analyzed for usage. If there's no pointer arithmetic, it can become a Rust reference, maybe with a "Some" if it can be nil. If there's pointer arithmetic, the pointer is going to have to be represented as a reference and a subscript.
You need a standard C library in Rust, with Rust safe equivalents of all the string functions.
It's a big job, but not impossible. It might be a marketable product.
From the author's perspective it is helpful. If the goal is to make an existing C project memory safe without sacrificing performance and memory efficiency, one way to do that is to rewrite it in (safe) Rust. An automated direct translation from C to (unsafe) Rust would result in a body of code that would not (completely) satisfy the borrow checker. But some significant portion of the code would, thus reducing the total work necessary to translate the project to safe Rust.
The thing is that you can satisfy the borrow checker by (manually) modifying the Rust code resulting from the automated translation, or, alternatively, you could modify the C code in such a way that the automated translation results in Rust code that satisfies the borrow checker. If you can manage to write C code that automatically translates to safe Rust, then the safety guarantees of the borrow checker also apply to the C code.
So the automatic translator can be used to apply Rust's borrow checker (and its safety guarantees) to C code. This would be one way to, for example, bring a degree of memory safety to embedded platforms that are not supported by Rust, but which do support C. Pretty sneaky, no? (Of course, some of Rust's memory safety is achieved through run-time checks rather than the borrow checker, so it doesn't completely solve the problem for C.)
But satisfying the borrow checker is still a lot of (mental) work, and as I've pointed out, there is an easier alternative. It would be much easier to write an automatic translator from C to SaferCPlusPlus[1]. And even for embedded platforms that don't support modern C++ (or at least its standard library), you can pull the same trick, as SaferCPlusPlus also uses a degree of static enforcement of memory safety (although not to quite the same degree as Rust's borrow checker.)
[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus
I felt this way too but then realized the advanced topics of Go really take time to get right. Writing correct concurrent code is non-trivial as you need to tailor your solution to the problem at hand. There is no one size fits all way to write concurrent code. In some cases a mutex makes sense whereas channels work better in others.
Programmers must be diligent when using these tools as well because of deadlocks, data races, or go routine leaks. I think this is an area Rust excels in with its ownership model that eliminates these problems.
That's basically my experience with rust and go.
go is stupid, but I can write it. It's stupid needing to write the same for loop over and over again, but it's an obvious for loop that doesn't have any surprises, and everyones for loop looks the same.
rust is smart, but I can't write anything in it (yet). Some of rusts features would be nice to have in go.
Really? Tell me what the output of this short bit of golang code is before running it then if it's so obvious. The first one is only a for loop, nothing else, the second output line is a for loop and some channels + goroutines, but nothing too complicated.
https://play.golang.org/p/g4Pd5ebrWp
I've seen these issues in the wild more times than I care to admit. Go is a trashfire. They wouldn't compile in rust.
It's literally about nothing but how the 'value' variable in go for loops is handled by the language.
To make it worse, a common solution is to do this: https://play.golang.org/p/BTRs_TCQdu
That code looks like line 10 shouldn't be needed, does nothing, and during someone elses refactoring it might be deleted and result in the code blowing up again. No warnings, just silent data corruption.
The second one is a bit complicated, yes, but again I've seen them in the wild. Again they wouldn't have compiled in rust.
When I write performance critical code in Rust I can see how I'm rewarded for all the mental gymnastics and token salad of Rust.
But when I'm writing something that's difficult or complicated in the business/algorithm sense but not performance sensitive, then I just can't let the syntax obscure the semantics.
The code has to look as simple and readable as ruby/go/Swift/c#/Python/kotlin/Nim -- or the language has failed me.
Right now I feel it's often the opposite. When I write low level Rust code the type system is usually ok. For higher level code I usually find I need more noise and type overhead (more explicit lifetimes, more Box<Rc<T>>). That I think is a big problem.
Rust, like C++ is "you pay for what you use" - but what they mean is performance. I wish it was readability. I almost wish the default was a higher level language with lots of heap allocation etc, and you explicitly declare when you don't want that (c.f C# unsafe/stackalloc and similar)
I don't know if that's really possible. Aliasable mutation just doesn't play well with the kind of memory model that Rust has. If you introduce unrestricted aliasing and mutation like most other languages, then you're going to get a flood of confusing borrow checking errors.
I suppose what I'm trying to say is that I'd really like simple code to look simple, and that it's an explcit design goal that code look as simple as possible. Perhaps I'm spoiled by languages that hide memory complexity with a pervasive GC (I am) but I still think there has to be room for improvement (and to be honest there probably are tons of already created issues with possible lifetime elision or coercions that reduce the need for Boxes inside Rc's inside boxes...).
Keep up the good work.
Nobody would consider a flute to be an instrument superior to a violin because it can be learned faster. Rust is a tool that takes time to learn. Is it worth it? How could ESR decide if not in hindsight?
My personal experience is that understanding and applying Rusts borrowing and ownership made me a much better systems programmer in C and C++. Would I write a production NTP server in Rust or Go? Definitely Go, but that's no reason to dismiss the concepts and the remarkable engineering of Rust.
In any case, Rust would probably require some rearchitecturing anyway.
But then again, it just takes time. And I have some crazy two month uptimes with my rust services eating that constant 8 megabytes of ram...
Given that Rust is younger than Go, ignoring a year's worth of development makes for an especially unfair comparison.
The issues he cited were from 2014 and 2015, regarding Rust for networking code. Moreover, on his blog post (http://esr.ibiblio.org/?p=7294) he received a clear answer that he seems to have ignored:
Stefano:
[...]
For epoll: There is a crate. You could also use
future-rs or tokio. So you have three possibilities.
Finally, the author of another networking project, TRust-DNS, used these Rust features and was quite satisfied with the elegance of the resulting code:https://bluejekyll.github.io/blog/rust/2016/12/03/trust-dns-...
More possibilities isn't necessarily better. ESR makes precisely this point: "While implementations exist in the crate system, there is not yet any guarantee that any of the alternatives will be maintained over 10-year timescales."
Having three possibilities just makes it more likely that you pick the wrong one, the one that will be dropped by its maintainers because the rest of the community chooses another one.
But I have the feeling Go became more of a Python/Node.js alternative.
I appreciate the safety+performance value proposition in Rust. More cathedral, less bazaar.
fn apply<A, B, C, F, G>(mut f: F, a: A)
-> impl FnMut(&B) -> C // must still be `for<'r> impl FnMut(&'r B) -> C`, because that’s what filter requires
where F: FnMut(B) -> G, // must not be `for<'r> FnMut(&'r B) -> G`, because regular functions do not implement it
G: FnMut(A) -> C,
B: Copy, // for dereferencing
A: Clone {
move |b| f(*b)(a.clone()) // this must do any bridging necessary to satisfy the requirements
}
this function signature must be EXACTLY like what I wrote or it won't work> this function signature must be EXACTLY like what I wrote or it won't work
This can be seen as a feature in the sense that you can't program by luck: well, this incantation seems about right and the compiler nods its head that it understands and that means something, so it must be right.
fn main() {
for i in 1..101 {
if i % 15 == 0 {
println!("FizzBuzz");
} else if i % 3 == 0 {
println!("Fizz");
} else if i % 5 == 0 {
println!("Buzz");
} else {
println!("{}", i);
}
}
}https://bitbucket.org/iopq/fizzbuzz-in-rust/src/bf4968973d73...
I hope their production code isn't like this.
If you want production code look at Rust and Servo.
.unwrap_or_else(|| i.to_string().into())
this is the only one that was too ugly to make point-freehttps://github.com/the-guitarman/rust_fizzbuzz/blob/master/s...
No, it's because the function you're returning is of type FnMut(&B) -> C, but your F function is of type FnMut(B) -> G. It expects an owned value of B, but you only have a borrowed reference to a B. Just make F of type FnMut(&B) -> G and you no longer have to dereference and no longer need the restriction that B: Copy. Higher-ranked lifetime values don't have anything to do with it.
Here's the new version:
fn apply<A, B, C, F, G>(mut f: F, a: A)
-> impl FnMut(&B) -> C
where F: FnMut(&B) -> G,
G: FnMut(A) -> C,
A: Clone {
move |b| f(b)(a.clone())
} error[E0281]: type mismatch: the type `fn(_) -> _ {tool::second::<_>}` implements the trait `std::ops::FnMut<(_,)>`, but the trait `for<'r> std::ops::FnMut<(&'r _,)>` is required (expected concrete lifetime, found bound lifetime parameter
--> src\lib.rs:50:17
|
50 | .filter(apply(second, i))
| ^^^^^
|
= note: required by `apply`
error[E0271]: type mismatch resolving `for<'r> <fn(_) -> _ {tool::second::<_>} as std::ops::FnOnce<(&'r _,)>>::Output == _`
--> src\lib.rs:50:17
|
50 | .filter(apply(second, i))
| ^^^^^ expected bound lifetime parameter , found concrete lifetime
|
= note: concrete lifetime that was found is lifetime '_#11r
= note: required by `apply`
error: aborting due to 2 previous errors
error: Could not compile `fizzbuzz`. template<class F, class A>
auto apply(F f, A a) {
return [f=std::move(f), a=std::move(a)](auto&& b) {
return f(std::forward<dectlype(b)>(b))(a);
}
}
The forward<decltype> is an abomination and is really in need of a language based solution, but otherwise is fairly straightforward.[1] No type checking of the definition of course, only of instantiations. On the plus side, the returned function is polymorphic and can be called for all Bs that f can be called.
https://bitbucket.org/iopq/fizzbuzz-in-rust/src/bf4968973d73...
it gives an error when I use a generic `second` method from tool.rs
EDIT: Reworded it to be more specific
Your solid grasp of the language may be different or the same as my hard to write. But neither would be confused with utterly ambiguous C. The counterpart to borrowing in C would be it's antithesis in Rust, aliasing.
That said I'm on my learning curve and my opinion could change.
I ask this, because newer versions of Rust have suggestions how to fix your code, which may get you out of borrow checker fight.
He could also use Java. It has a 'pauseless' GC in Fedora Core's OpenJDK (Shenandoah). It has an epoll abstraction in the core library. It has libraries for doing things like async DNS requests already.
It would be nice sometimes if these A vs B comparisons were a bit wider.
> It is worth noting that Go came nearest disqualifying itself entirely here. If it were not possible to lock out Go’s GC in critical regions Rust would win by default.
If this is the option, I hope the critical sections in ntpsec are few and far apart...
[1] https://golang.org/src/runtime/debug/garbage.go?s=3706:3740#...
[2] https://golang.org/src/runtime/mgc.go?s=33685:33694#L844
I mean, technically anything the system or runtime does on your behalf can be thought of as a 'pause'. What if you lose your timeslice half way through a function?
> Shenandoah is an ultra-low pause time garbage collector that reduces GC pause times by performing more garbage collection work concurrently with the running Java program
Funny enough, the term "pauseless" was in fact invented precisely because of people like you, who abused the original terms ("concurrent" or "asynchronous" [0]) used for garbage collectors that never "stop the world", and used it for algorithms that merely run in parallel with the mutator threads, pausing them every so often. The next term used was "fully-concurrent" and now "pauseless". But if you keep abusing these terms to mean not fully pauseless/concurrent GCs, then it will become just as useless as all the terms before it.
This language seems unnecessarily personal and accusatory.
So, if you are operating in a environment where you don't care about thread switching, you might also not care about GC anymore.
You are correct however, its not pauseless, that just a marketing term.
There's a talk on YouTube where they discuss their work to drive down safe point pause times. They eventually made the pauses so fast it hardly matters, but it wasn't technically completely pauseless.
On the other hand, Shenandoah has an explicit synchronisation phase in its algorithm (not sure if that's a fundamental property of the algorithm or just a JVM limitation though). From [0] (that was 2 years ago, things might have improved since, but I can't find a more recent version).
> Pauses only long enough to scan root set
[0]: https://rkennke.files.wordpress.com/2014/02/shenandoahtake4....
http://stackoverflow.com/questions/3770457/what-is-memory-fr...
> Azul Systems has built a custom system (CPU, chip, board, and OS) specifically to run garbage collected virtual machines. The custom CPU includes a read barrier instruction. The read barrier enables a highly concurrent, parallel and compacting GC algorithm. The Pauseless GC algorithm is simple, efficient (low mutator overhead), and has no Stop-The-World pauses.
Azul is very similar to Shenandoah we are talking about. Updating of a wrong pointer still needs time, which can be unlucky when write trap is triggered in the middle of moving of large object.
However, whether we like it or not, Azul has established the term "pauseless" as the way to describe their own collector which before Shenandoah was the only GC that can realistically be described that way, and which Shenandoah is very similar to. This is despite the fact that Zing still pauses. Specifically, the GC moves objects whilst the program runs.
I am unaware of any GC algorithms that are truly, completely pauseless in every situation. So I don't know what you want the term to be reserved for.
It's a replacement for one of the many background UNIX services, not a "primary" service that can own a machine.
That doesn't really say anything about viability for an open-source project that's supposed to run on all kinds of embedded and general-purpose hardware.
I think sometimes it makes sense not to shove Java where it is not applicable.
http://www.slideshare.net/RedHatDevelopers/shenandoah-gc-jav...
It's true you'd need a JVM on the platforms where ntpsec runs but that's also true of having a port of the Go runtime.
I mean he also says that rust has no good epoll/select abstraction, which is wrong. And also "a welter of half-solutions in third-party crates but describe no consensus about which to adopt" I think he didn't researched well enough.
I mean it would be the same as saying that there is no good way to create a custom non blocking dns server with pure java/scala (no external library used). Rust has https://tokio.rs/ and everbody who even looked into network services with rust, stumbled across it.
Well maybe for his goals and contributor base go is prefered and that is ok, but well this article is more or less written as a rant against rust, that he/she/it does not agree with the generel design/documentation/whatever of rust.
In fact I also think that the tooling of rust sucks. rust is a language which shines with extremly good tooling. I mean rust has a godlike package manager and every tooling gets better and better.
Compared to go where the package management just sucks but the tooling even IDE completion is extremly great.
P.S.: I would not want to have a garbage collected ntp server. But for a lot of things I thing Go would be a good fit, even when it would not be my tool of choice.
I do think that the majority of the resistance though is that the author could not get productive with Rust in 4 days. For that reason, even though the epoll and documentation issues might clear up, I don't think the author would ever select Rust, not matter how much time has passed.
Which as I write, I realize that I think that Rust would be better, lack of easy on-boarding aside.
That said, as pointed out in the previous discussion about this, C also doesn't have epoll in the core language (it also isn't in the standard library there, either), and Rust can call the C epoll functions directly on the platforms that have it.
"Announcing Tokio 0.1 ... The 0.1 release is a beta quality release. ... This release also represents a point of relative stability for the library, which has been undergoing frequent breaking changes up until now. While we do intend to eventually publish a 0.2 release with breaking changes ..."
I'm not trying to take a dump on the project (breaking changes are expected and welcome for new projects), but it is obvious a lot of the Rust ecosystem is in a very immature state. Probably much to immature to base an ntp server on.
ESR knows this. It was there in the comments of his previous issue. Yet he chooses to link to a two-year old issue with one comment that says that Rust doesn't have epoll as a source of truth.
Mio can't compare to the baked-in goroutine goodies (neither can epoll in C!), but this blog post is outright dishonest.
Again, not taking a dump on mio because its authors are doing the right thing. 0.x means bugs and api breaks are to be expected. Use the software at your own risk. Basing an ntp server on it might not be appropriate (atm).
But yes, the async I/O space in Rust isn't amazing. But it's not as bad a picture as he paints.
Pinning a version number is a hack. Not something you should rely on for long-term stability.
0.x can mean "Not stable" and it can also mean "buggy", it need not mean both. Regardless of how bug-free the authors consider it to be, they won't mark it 1.0 until they think that they're happy with the API. IIRC they're waiting for tokio to be finished and for it to sit for a while to ensure the API is the right one, so it should be relatively free from bugs.
> Pinning a version number is a hack. Not something you should rely on for long-term stability.
Agreed. I'm saying that this shouldn't be a dealbreaker. It's usually very little work to upgrade to the latest version of a library.
Basically, mio's stability is a problem. Totally agree with that. But it's not as bad a picture as the article paints.
Keeping a package at 0.x means (to paraphrase) "I don't take responsibility. The code may or may not work and if it doesn't, then don't complain." (a) Putting it at 1.x means (to paraphrase) "I (the author) will at least respond to bug reports. I might even try to fix them if I have the time." (b)
Free software authors have this choice.
> so it should be relatively free from bugs.
And if it isn't? For example, I looked at the tracker and found a number of open bugs which looked troublesome for the Windows backend. I think you are doing the mio project a disservice by promising more than the developers themselves have said that they are willing to deliver.
Fair point about the bugs :) I wasn't aware of them, and I've only heard really good things about mio.
That means 0.x software is in "initial development" implying no guarantee on how many bugs it can contain and no guarantees regarding maintainership. Other parts of the spec deals with backwards-incompatible api changes.
I know where your interpretation comes from. It comes from users of software who believe that a 0.x version can be relied upon. Because it works for them and they haven't found any bugs. Software publishers on the other hand, never makes that claim and users to whom stability is important should stay away from 0.x software.
I can't reiterate this enough. Rust has one of the best abstractions I've ever used over epoll/kqueue and MS' variant, MIO. MIO has picked not the crappy POSIX select, which every major OS has a better alternative to now, but instead the OS' preferred event system. The 10 year stability requirement is awkward, as I don't think anyone can predict the future, and in 10 years, who knows what will change.
As you mention, tokio is an awesome futures based abstraction on top of that, making it a breeze to write complex, fast, event driven logic.
I'm sad that after four days he didn't like Rust, I had a 100% different experience. After one day I fell in love with nearly every aspect of the language, and it was specifically because of state machines (which they mention as one of their core desires). My biggest complaint against the language hasn't changed, which is dealing with different Error types, but even that after a lot more experience with Rust has become easier.
Anyway, for NTPSec it's sad that this didn't suit their desires, but that doesn't mean that we can't write our own.
Do you mean syntactic sugar? Because there's try! and ?
Or do you mean dealing with Option and Result in a more uniform way?
Mind you, this is my biggest complaint, and it's trivial for me now, but after you grok the other major features of the language, properly dealing with errors/results is a hurdle for people new to the language.
When comparing to other libraries in other languages, MIO probably looks like it's devoid of features, but that's because it's trying to be an abstraction layer only, and tokio is probably where most people should turn for higher level semantics...
And that for me that is the way I want to do concurrency and parallelism for pretty much everything I'll ever write.
1. You can get exactly this with concurrency by spawning one thread per request. The only difference between Go and the pthread model is that Go has a userland scheduler.
2. If I tried to do my parallelism work using this model, the amount of multicore speedup I would get would be zero (actually, it would be negative). Sometimes you're compiling multiple .o files--you get lucky and the overhead of spawning a thread per work item is not restrictive. Sometimes you're shading individual pixels--spawn a goroutine per pixel and you're going to be laughably slow.
The only difference between Go and the pthread model is that Go has a userland scheduler.
That's a _huge_ difference in the context of a network service, and also obscures some other details.If I have several thousand long-polling clients, one kernel thread per client is simply not realistic. It uses up alot of memory, and the context switching can be costly. Throwing more hardware at the problem is just throwing money down the drain. And if throwing money down the drain is fine, one might as well use one process per connection with blocking I/O.
In order to avoid that in Rust, Node, or most other languages one needs to use a callback or future. But both a callback and future require a dynamic allocation and deallocation per invocation of each function that might block. Especially under heavy load (lots of broadcasts), that's alot when you consider chaining invocations, as you must when composing non-blocking I/O interfaces. Chaining futures hinders compiler optimizations. And in general it hinders function composition. (I commonly see claims that the react pattern is better for composition, yet I never see such people writing _all_ their code that way, e.g. for things like string manipulation. In reality most concurrency, even in network services, is effectively sequential within a wider context beyond the immediate operation. Have you ever tried to implement, e.g., a non-blocking MySQL protocol parser with callbacks/futures? It's ugly.)
People underestimate how efficient and useful storing function invocation state on a stack is. Goroutines (or contiguous, dynamically-sizeable stacks like in Lua) give you the best of all worlds--efficiency of a contiguous stack without the high fixed costs. That means performance _and_ scalability for massively concurrent network services, but also for other patterns--like being able to convert a callback-based push interface into a pull interface, without having to refactor the intermediate code, which can be amazingly powerful for things like parsers.
But for a language like Rust it's understandably difficult to implement stackful coroutines, let alone goroutines. I just wish people wouldn't whitewash the issue.
The three main reasons that Go doesn't fit with the work I want to do on the network is all around compile time guarantees:
1) Strong type checking on Null 2) Required handling of Errors 3) Generics
These all help me write stable software that I have higher confidence in than what I've written in Go.
In terms of the debate on Futures, etc. you are right that it's simpler to write pure stack based functions, but the overhead of tokio and futures in Rust is not as high as that of a goroutine, but it does take more thought. It's a trade off on productivity vs. stability. In your opinion it's ugly and hurts productivity, but to me it is pure elegance with zero overhead cost at runtime (and if you preallocate an arena/slab it can even have known memory costs). After working with tokio over the last half year or so, I find that I'm as productive as I am when writing single threaded blocking IO code (granted there was an initial ramp up time before I became as comfortable as I am now).
The memory cost of pthreads that people generally refer to is the stack. Userland scheduling doesn't have anything to do with the stack size. You can get the stack size down very low in a 1:1 thread model: 10kB (or, in future Linux kernels, 6kB). That's comparable to the stack size of a goroutine.
Context switching cost depends on your use case. For I/O, keep in mind that you have a round trip through the kernel either way.
> being able to convert a callback-based push interface into a pull interface, without having to refactor the intermediate code, which can be amazingly powerful for things like parsers.
You can efficiently do that with threads too. In fact, that is exactly how HTML parsing works in every modern browser: the parser runs off a separate thread and forwards DOM create events to the main thread. HTML parsing is about the most performance-critical parsing setting you can think of.
didn't it launch a week ago?
Go also offers a lot of high level constructs that allow you to create a web server and other solutions traditionally built in higher level platforms, but that doesn't reduce its low level functionality.
Rust has escape analysis and reference counting. It really isn't so different.
In terms of integration with native code, the concurrency model of Go means calling native functions has high overhead because it has to switch to a native stack (thi iss one possible motivation for why Go does syscalls directly on Linux: avoid the stack switching overhead).
Go makes the stack/heap decision at compile time as well. It is far closer to C++ than Java. To your other point, cgo calls indeed have a high overhead in an absolute sense, and ideally you do fatter, more meaningful calls.
I am not advocating for Go, it just seems that these discussions often pigeonhole technologies, and a common one is the strange belief that Go is just another Java/C#, and Rust is the next low level language.
There are many other aspects in which Go does not offer as much control as Rust, e.g. the former incorporates a runtime and garbage collector. This isn't disagreeing that Go offers more control than Java, but Rust (and C++) offers even more than Go.
No, it doesn't! This is even in the FAQ: https://golang.org/doc/faq#stack_or_heap
C# and Java have pauses in the hundreds of millisecond range (though there are "real-time" JVMs that greatly improve this). And it's notable that Go goes to lengths to avoid generating garbage.
If you are writing a library to render the latest web image format, or implement the latest secure network protocol, then your choices are limited and Go is not an option.
Sure, you could write it in Go and then Go programmers could use it.
But if you write it in C or rust, it could be the de facto library for nearly all languages.
The article is a good one, but doesn't really influence my personal opinion much.
This is how hard adding String objects is in Rust:
a + b
A more full example shows only small complexity, really, and I include it only for completeness and fairness: fn main() {
let a = String::from("Hello,");
let b = " world.";
let c = a + b; // this is the only part that concats, really.
println!("{}", c);
}
The String::from call converts from a str& — which is effectively a pointer and length to UTF-8 data, to a managed string, more akin to std::string in C++; str& doesn't implement +, I presume b/c it doesn't know what the resulting type should be (and doesn't presume).A C programmer should easily comprehend why you can't simply add two char * s together; compare:
char *new_string = malloc(strlen(a) + strlen(b) + 1);
if(!new_string) { abort(); }
stpcpy(stpcpy(new_string, a), b);
return new_string;
(that's just the `a + b` line from Rust, in C, essentially)(hopefully I got that right. stpcpy avoids an extra iteration through a that strcat cannot; I do know about strcat.)
Essentially, in all of Python/Go/Rust, if you has the right type, string concatenation is `a + b`.
> Contemplate this bug report: Is there some API like "select/poll/epoll_wait"? and get a load of this answer:
> > We do not currently have an epoll/select abstraction. The current answer is "spawn a task per socket".
It would need to be cross-platform; so I can understand why Rust isn't there yet in the standard library. There are third-party libraries out there that do this. (And C has nothing here, either…)
You can always call epoll directly, and write the required abstraction yourself. (There are even third-party wrappers for calling epoll, so you don't even have you write that yourself either; see the excellent "nix" library.) If you want a 10 year solution…
> Relatedly, the friction cost of important features like the borrow checker is pretty high.
At first, this is true. Then I felt like I started realizing that what all the "friction" of the borrow checker was it pointing out serious bugs in my code.
Regardless, NTP would be better off in either Rust or Go instead of C, IMO.
fn main() {
let a = "Hello,";
let b = " world.";
let c = a + b;
println!("{}", c);
}
And the result is: rustc 1.15.0-beta.3 (a035041ba 2017-01-07)
error[E0369]: binary operation `+` cannot be applied to type `&str`
|
5 | let c = a + b; // this is the only part that concats, really.
| ^
|
note: an implementation of `std::ops::Add` might be missing for `&str`
|
5 | let c = a + b; // this is the only part that concats, really.
| ^
timeout triggered!What?
PS. looks like hacker news can't handle rustc error format, a pity...
Your code fails to compile because it attempts to apply + to two string slices, and that is not implemented. @deathanatos also mentioned that.
You might find the Rust book page on Strings helpful: https://doc.rust-lang.org/book/strings.html
I remember learning Java was a piece of cake. Go was too. Both were remarkably similar to previous languages I knew. Rust was/is a lot more difficult, but nowhere close to the mindfuck that is Haskell or ML.
I mean, is it that bad that it takes a month to really learn a new language if it can provide worthwhile benefits?
It only takes around a week or so though, after that you can be surprisingly productive. I never would allow myself to write the code in C that I wrote in Rust. I'm just not clever enough.
C++ helps, if you are familiar with RAII usage in it.
Had the same feeling with go. Looking into big projects like syncthing helped a bit. But first i have to get a better hang of the language. Books like "Atomic in Scala" or "Clojure for the Brave" are taking your hand and guiding you through the Language. Even when taking a peak into books like "21st century C" i learned more, then when i simply force myself through go's "Gopl" or Rust's Book. I know i am probably not the right target group. But i really want to learn one of those languages in my free time, and i had a really hard time to do so.
After understanding go better, i still want to switch to rust. The build system with cargo pleases me and looks somehow cleaner as in go. I just create a project with 'cargo new XXX --bin' and start coding and building some kind of lib in my project. No problem. I go there are 100 different ways to start a project, and most time you will only find people complaining about vendoring etc.
I have quite a few openwrt routers running with substantially less resources available to it than the worst android phones.
I don't know if go would be able to meet the cpu/memory constraints in that environment... from the blog post it seems like that wasn't even measured?
I think it's largely because C and C++ has been used in such a wide variety of applications as "good enough" languages but where there were so much room for improvement, for modernization. We're talking about an old assembly language layer and an object orientation cludge on top of that layer. As soon as you do improve on these, you observe that the application space to cover for successors is huge, so huge that there is plenty of room for two different languages.
Developing in Go doesn't feel like Java or C# to me. It feels more like C (and definitely not C++) and I love that. It's so simple, yet producing native code. It's like what you'd get if you took C and from the very start decided the productivity vs performance problem favored a garbage collector, prefering productivity. Then looked at how common and underutilized multicore CPU's are, while at the same time how synchronization is hard to get right. If you then move from these problems to solve and realize the GC is your Achille's heel and simply try to optimize the heck out of that, I guess you get something like Go.
Meanwhile, Rust during the design phase must have been in such a similar place as Google engineers were when designing Go? Once again, C / C++ were flawed and unsafe, cumbersome to work with. They took the very same productivity vs performance problem and this time saw that, no, we don't want garbage collectors, we prefer performance supporting time critical systems above all in the spirit of C, yet safety. Then looked at common crash issues and went by that, and then they got Rust.
Personally I prefer Go. It's the most fun for me to work with and feels like a very pragmatic language. Rust feels more like an academic language to me: ideas and exciting concepts allowed to materialize. It also has its place, but seems like more in niched scenarios like real time systems and device drivers, low level, critical stuff like that.
the Rust team often pretends they are open to suggestions as to how to make Rust easy or easier to learn, that's impossible given how memory management is done in Rust, especially in regard of the people i talked about in the former paragraph.
Only a developer familiar with C or C++ can appreciate Rust semantics. The others cannot.
You just proved my point. You did get into C and C++ before trying Rust.
In particular, it's my understanding that epoll and select are system calls. In that case, just call them. Or is there really no means to do so from Rust (doubtful)? Sure, you'd have to wrap stuff in "unsafe" calls, but that's par for the course when interfacing with non-Rust software.
I don't think 4 days is enough to really evaluate a language. I'm not going to dismiss Rust based on this analysis.
It's the 4th law of HN: Wherever Rust is, Go goes, and vice versa.
I am considering just accepting this reality that people will always pit these two against each other, and maybe just accept it for it's positive merits: that the extra competitiveness will help create a better ecosystem for the two of them.
Well, unless you want people to actually use your shiny new language. If that's the case, perhaps criticisms of this type should not be dismissed out of hand. :-)
Most programmers are going to abandon something if they study it for a week without significant results. Maybe they'll keep plugging at it if their boss orders them, but they won't do it on their own. Oh, sure, there's a small subset of programmers who enjoy tinkering with weird languages for fun, but that subset is dwarfed by the number of programmers who view languages as a tool for getting things done.
If there's anything that we should learn from the history of programming languages, it's that usability trumps theory every time. The languages that people actually use are almost never the ones that theoreticians admire.
Note that most code today is written in C, JavaScript, PHP, Java (none of which would win any prizes for theoretical elegance)... rather than ALGOL, ML, Haskell, the various Wirth languages...
There's really no comparison. Lisp probably comes closest, in that it is both theoretically elegant and has been used, by the programmer's choice, to build large real-world systems, but even Lisp is basically a rounding error.
ALGOL wasn't actually that bad.
Pascal was popular for teaching at one time, but pure Pascal was never very popular for writing shipping code. If I'm not mistaken, Borland's Turbo Pascal, which was pretty popular, became so by adding non-standard features that probably gave Professor Wirth a heart attack when he heard about them.
- Dreamweaver
- Skype
- TOAD for Oracle
- TeamSpeak
Etc.
The sort of people who find major problems in Go simply won't put time and energy into the language beyond a point—why should they? There's a limit to how far I'm going to explore a tool that seems obviously inferior, even if the tool is popular. (And popularity is an absolutely terrible measure of quality!)
If you applied this standard to PHP, you'd get an implausibly rosy view of the language, because the people who aren't invested in it simply don't use it.
(Not implying Go devs are racist, I hope that's just ESR.)
edit: oh, the The Cathedral and the Bazaar dude. i saw a video about him that seemed to portray him well. but having found his blog... well... i can understand your sentiment.
Hm.. Title is "Rust vs. Go" - looks like title of another one holywar flame article.
Which would ultimately be fun to follow
I've tried Rust and concluded that it solves problems I don't have. I work mostly in C++ (more like C+, I tend to keep my code pretty simple), and with unique_ptr<>s, shared_ptr<>s and judicious use of concurrency primitives I can avoid pitfalls well enough that I maybe encounter a self inflicted concurrency or memory management issue once every six months, if that. And it pays amazingly well, and there's an enormous number of libraries available. That said, when I need a high performance web server, I'll use Go without hesitation. When performance doesn't matter, Python and Flask are pretty indispensable.
Most C++ developers I talk to about Rust express the same sentiment, they usually like the idea, but aren't personally interested in learning it because it solves problems they're rarely confronted with.
I tried picking up Rust a few times since it does interest me, and I like trying and learning new things, but I currently don't have the time to really dive into it. Go on the other hand took no time at all to get into, and was promoted pretty quickly into one of the more frequently used tools in my toolbox, just like Python.
I've been writing Javascript for years and if there's one thing I've learned it's that type safety matters on the large scale. Don't get me wrong: I actually love Javascript. It's my go-to tool when I want to just get things done. But when you start scaling things out and get a lot of moving parts going, type safety really helps lift the mental burden of tracking everything yourself. That's why TypeScript is so popular.
I've been a huge fan of dynamically/weakly typed languages for a while but I've fallen in love with Rust. Yes, it can be a bit painful at the start but it doesn't take hours to get basic IO working and it's quite beautiful once you get into it. The mind-bending part is the ownership model because nothing else has ever had something like that. But, you know, that's just my opinion.
What it fails at is making the simple things simple. Making a program that concatenates some strings shouldn't be hard, and the result should be clean and readable. That obviously hasn't been a design goal yet, but I hope the focus will turn to making tresholds lower and syntax less painful soon.
#include <stdio.h>
int main(void) {
printf("hello world\n");
return 0;
}
Rust: fn main() {
println!("hello world");
}in go, you can download it and have the pieces to actually do useful stuff right away. legit productivity. not to mention putting together graphs or graph-like structures and other similar things aren't the obtuse sphinx riddles they are in rust.
End of the comparison. That's all one needs to know if he's interesting in a career in distributed systems.
[1]: https://this-week-in-rust.org/blog/2017/01/17/this-week-in-r...