I like Rust so far, but there's a few things I think aren't true:
* That Rust is only harder because it enforces 'correctness.' It certainly is harder because it enforces correctness, but it's also harder because of how. I'm not saying there's a better approach to this, but I think a lot of people are implying that there isn't, and I don't think that's a safe assumption. I think that we could find ways to make equally memory-safe languages that go about enforcing safety in entirely different manners than with ownership and lifetime semantics.
* In fact, the entire idea that Rust enforces correctness. Only if your definition of 'correctness' to be memory-safety, but I would normally define 'correctness' to include rigorous mathematical proofs. Rust's safety guarantees are often accidentally blown out of proportion; they mainly aid in preventing security and concurrency bugs, but only a certain class of each. This is still useful, but this caveat really needs to be in your face more often, as a lot of people will not mention it when touting the benefits of Rust, and beginners can get easily confused about what exactly Rust prevents you from doing.
* The idea that Rust's approach is always worth the trade-offs. Go is another programming language I like, and there are definitely things that are simply easier to write in Go with few disadvantages. Fearless concurrency is a wonderful feature, but for embarrassingly parallel problems like, often, web servers, where each thread is usually independent in terms of mutable state, Go works wonderfully. It also lets you shoot yourself in the foot in a way that Rust wouldn't, but often for a lot of simpler apps it still ends up being easier.
* The idea that solving the compiler errors makes you understand the problems correctly. For example, you could always just clone memory at every occasion, return the input instead of borrowing, etc. In fact, these things might be easier for a beginner to do. There will probably be a ton of Rust anti-patterns that come about from trying to resolve compiler errors.
> Go is another programming language I like, ... Fearless concurrency is a wonderful feature, but for embarrassingly parallel problems Go works wonderfully.
I adore Go's concurrency model but loathe go's actual language. The constant repetition in error handling, lack of generics and lack of parameterised types and Option<> make it feel like a children's toy set version of C instead of a useful modern language akin to Rust and Swift (and modern javascript).
I really really love the concurrency model though. And as far as I can tell there isn't much like it available elsewhere. You can make a similar concurrency model in rust, but you have to use much heavier OS level threads to do it, and all the other crates don't support it. Erlang / elixir do it but come with a much higher runtime performance penalty. Pony is interesting but very new - there aren't a lot of libraries for it and when I was playing with it the compiler seemed crazy slow.
I'm a little bit tempted to make a simple compile-to-go language. I'd get lynched at Go meetups for fragmenting the ecosystem, but it might be worth it. I like almost everything about go except the language itself.
I'm a little bit tempted to make a simple compile-to-go language. I'd get lynched at Go meetups for fragmenting the ecosystem, but it might be worth it. I like almost everything about go except the language itself.
I would love this; I feel entirely the same. The runtime is pretty good, performance is nice, concurrency is great, the tooling is wonderful (if you ignore the GOPATH nonsense and thew lacklustre package management solution). The language itself is quite frustrating to use, full of needless repetitive boilerplate and a mediocre type system.
I'd love a similar language with all the goodies that I find make development safer and more productive: proper enums, pattern matching, sum types, generics, and a handful of other features.
The Elixir performance penalty compared to Go isn’t as big as people think. It largely depends on what you are doing but the perk that your get is consistency of response time.
This is one of the better articles that shows both when comparing Python, Go and Elixir.
The nice thing about Rust not having Async IO built into the language is that arbitrary third-party implementations are possible on a level playing field with the async framework being developed by the core team.
For example, there is the May[1] concurrency library. It provides alternative implementations of the standard library's IO interface, but does Go's automatic suspend/resume, so it still looks like blocking code, which is nice. From what I can tell it's still early days on a one-person project, but it is interesting, at least.
Let me offer a counter point. Every language I have used that has gone the route of "let the community make their own concurrency libraries" has turned out a mess; specifically Ruby and Python.
On the other hand, the concurrency OOTB languages have all had much better ecosystems and experiences; C#, nodejs(now and w/TypeScript), F#, Golang, etc.
"Batteries included" is almost always better than "level playing field" because of network effects. The larger the set of common types / protocols available, the higher up the abstraction stack interfacing between two unrelated third party components can be. Minimalist environments have to pay taxes in the form of glue code and adapters for different ways of representing the same underlying concepts (though the costs are reduced in duck-typed languages). Even worse things happen when you try and use two third-party components and they share different versions of a common dependency - sometimes there's no easy way out.
Inferior approaches sometimes do get baked in to standard libraries. Having a culture of versioning, deprecating, migrating, is better IMO. But it's even better again to do a really good job first time around. Not easy, but I didn't say it was!
> I'm a little bit tempted to make a simple compile-to-go language. I'd get lynched at Go meetups for fragmenting the ecosystem, but it might be worth it.
I don't know about "might be worth it" part, and I don't think Go is success is because of it's concurrency model. Instead, I think Go wins because it can get thing done quickly, in one stop.
The concurrency model has been designed to help achieve that, and the huge battery pack came with the language is also for help achieve that.
I'm not discouraging you from design your language, though. In fact, I could be very happy to see a new language which can give me all the benefits that Go gives me and at same time just ... simply be a better language.
My ideal language (in my opinion) is a combination of Go and Rust: Big standard battery (Help me get things done and encourage to it's ecosystem)(&BTW, it don't have to be in the standard library), and fearless programming (Help me avoid mistakes).
Sadly Rust don't want to have a big battery for some reason :(
Rust does want to have a big battery. There have been works about promoting some crates as "the" tool to handle certain tasks. See the rust cookbook[0].
Rust is against pulling it in the standard library because that forces them to make additional stability guarantees. When you do that, you end up like python's `urllib`/`urllib2` situation.
> I'm a little bit tempted to make a simple compile-to-go language.
That's what I did for my current project. I love Go but for web apps is not the best in my opinion. I had some spare time and ended up building a VM that compiles typescript to bytecode. The performance loss is minimal, I still use the Go std library but it is a pleasure to use VS Code, generics (the VM ignores types but the TS compiler gives you static type safety, autocompletion, refactoring, etc...) Also exceptions is great for web apps where usually there is nothing else you can do but save all the info that you can and show an error to the user.
I write somewhat simple programs and webapps for my job, from time to time. I use Python and its standard library, some modules, and the Bottle Framework. Pulling data from APIs, doing analysis, taking some user input, editing configs, etc.
I hardly ever use classes unless I'm extending a vendor library. I have never used generics. Why are generics such a critical component of a programming language that every thread about Go mentions it? It's an honest question from me.
Python is a dynamically typed language so when you say you've never used generics, that makes sense -- the concept does not apply to dynamically typed languages. But I bet you frequently use lists and dictionaries, and perhaps occasionally use higher-order functions like map, filter, and reduce. All of those would be generics in a typical statically typed language, because with static typing you don't just have "a list" but "a list of ints" or "a list of strings".
Go has built-in generic arrays, slices, and dictionaries, but nothing else, and you can't define your own functions that work on those without specifying the type. So say you write a function that shuffles the elements of an array, like Python's random.shuffle(list). You can't make your function work for all kinds of arrays. You have to have a different function to shuffle arrays of strings, arrays of ints, arrays of FooBarClass, etc. even though the shuffling logic doesn't give a damn about what sort of thing is in the array.
I have a suspicion that this occasionally leads developers to write code inline that they previously would've extracted into a utility function. See the answers here:
Thanks. I took a year of CS ten years ago and haven't had to do statically typed development of any size since then - certainly not developing libraries.
So essentially, containers and function overloading is damned near impossible. Got it.
Python doesn't need generics because it doesn't try to statistically type your code in the first place.
def example(a):
return a
Would be perfectly legal python code. But in Go you would have to choose the type of A and duplicate the function under a different name if you want it to work with a different type.
So, when a language implements static typing without generics they actually mean 'lots of code and approaches legal in Python would be rejected'.
So it doesn't add a feature to the expressiveness of the code. It fixes a bug in the type system so that the expressive code is considered legal. Of course better type systems have existed since the 70's, but the set of people that know the ins and outs of how to implement those and the trade offs behind it does not include Rob Pike. His interests and skills are different (and the cause of some of the better features of Go)
When you don’t have type safety you don’t need generics. Example: in python you can call a function with any arguments (numbers, strings, whatever) and it can return anything (usually something of the same type). With go you can do this as well, but you lose compile time type safety, have to add a bunch of gross code, and incur a small performance penalty.
> Why are generics such a critical component of a programming language that every thread about Go mentions it?
Personally, I do not feel that strongly about generics; it would be nice to have them, but for my purposes, I can live without them.
But still: generic container types would be very nice. I don't need to use it very often, but Go's sort.Sort is very uncomfortable to use. If Go had proper generics, it would be easier to define a "generic" interface to iterate over things - currently, Go's builtin slice and map types are privileged over user defined types. There are probably more issues I cannot think of right now.
None of those are deal breakers for me. Go is highly compatible with the way my mind works, to such a degree I can easily forgive it all the things I do not like about it. But I also work in C# from time to time, and seeing how the .Net framework uses generics makes me wish Go had them, too.
Here's a framing I found useful: as a user of a library, you may not particularly use generics. But as an implementor of a library, on the other hand, they're extremely useful.
However, comparing to Python won't make much sense; people see generics as essential to statically typed langauges. You don't need them for dynamically typed ones!
> Here's a framing I found useful: as a user of a library, you may not particularly use generics. But as an implementor of a library, on the other hand, they're extremely useful.
Agreed. Something that's been underlined for me since picking up TypeScript in addition to JavaScript. I can take or leave TS when writing a script, but I consider it indispensable when writing a library.
In Python, duck typing provides the benefits of generics, minus the static guarantees. If you're using duck typing in python, then you should be able to understand why generics can be useful.
Go uses structural typing which feels like duck typing but it's actually checked at compile time.
That's the reason why the Go community feels generics are not the top priority (euphemism.)
Only in very specific cases (implementing data structures, for example) you can feel the need for generics. Maybe also in serialization, although that's really normal to pass a generic container (object, void *, interface{}) and use introspection.
You can't fix Go directly. You have to convince the language designers that it needs fixing. The fastest way of doing that would likely be at least a proof of concept, or better yet, a complete tool that gets significant community uptake.
> Pony is interesting but very new - there aren't a lot of libraries
The lack of libraries is what drove me away, too. The type system is gorgeous, though. I really hope Pony grows a decent library ecosystem soon, the language itself was very pleasant to use once I got past the initial learning curve.
> I'm a little bit tempted to make a simple compile-to-go language. I'd get lynched at Go meetups for fragmenting the ecosystem, but it might be worth it. I like almost everything about go except the language itself.
If you rephrased that as 'a templating language for generating Go code', it would slot into the 'go generate' build stage and people would love you (if you got it right ;) ). The only current solution to boilerplate and lack of generics is code generation, but the templating languages all seem to suck for Go code generation so people are stuck with writing Go programs to generate Go code (like the various enum generators), or attempt to marry Go code with the standard text templating which is awful to work with (like, say, Xo).
I haven't used Go all that much, but arent the interfaces meant to be used as generics, i.e. in your function you need some data X, and you'll do something with that X. The way you need to achieve this is that expect an X that satisifes a given interface, say Exampler. If a particular X does not support it, you write the method(s) on X's type to satisfy the Exampler interface. IIRC Pike said somewhere that you don't need generics because there should be at least a singleton interface that all possible values of a function parameter satisfy.
There is certainly some overlap between those features, but I think they largely cover different usecases.
For example, consider trying to implement a List data structure in Go. What type does the List hold? It poses no constraints on the type of things put in it, except that they must all share that type.
The List can store `interface{}`, but then there's nothing to stop you from adding two different types to the list. And what type of value would a method like `getFirstItem()` return? Just an `interface{}`, forcing the user to cast.
Go's interfaces express constraints on individual types (e.g. type A has methods Foo and Bar) but not across functions defined on a struct (e.g. the type that a List stores is the SAME type that the getHead() function returns).
Pike clearly doesn't even believe his own argument because he added generics for lists and maps. He just doesn't allow users access to the same capabilities.
On the other hand we ripped out channels and went back to mutexes. And my experienced Go-developer friends seem to have all the same reluctance to use channels after ending up in channel hell.
There are good aspects of Go concurrency like how the whole ecosystem is async by default (like Node's), but truly praising the model is something I mainly hear from beginners.
This is something I noticed with Go. Channels are great and all and occupy a decent chunk of the tutorials and whatnot, but they see to be rarely used apart from a fairly cumbersome way of handling timeouts and cancellation. It seems if you stick concurrency in your libraries you end up in knots, and its left up to the code that uses the libraries to wire up all the synchronous & blocking bits.
I don't know Go, but could you point to more details on the "channel hell"? I would have assumed it was a good way to structure parallelism so I would like to see why not.
You make some good points but I think it's incorrect to compare Rust & Go. Rust is a systems programming language. It competes with C/C++ more than other high-level languages. In fact, while Go was originally positioned as a systems language but it ended up attracting people from scripting languages like Python because its performance characteristics put it there. You'd probably never bother building a serious web browser in Go, but you would (& Mozilla is) in Rust.
In terms of correctness, I've never heard claims about improving security issues, except in so far as those caused by memory/concurrency - think of it as necessary but not sufficient for security. This "limited" class of bugs is responsible for quite a large number of runtime issues & they can be frustratingly difficult to find/fix (+ be confident that you did actually fix it). It's hard to say how much better things will play out in real-world software development as at some fundamental point there are always unsafe calls which weakens the guarantees Rust can make (but does put an explicit boundary on where you should go looking for bugs).
As for compiler errors not helping you understand the problem, I have yet to encounter a compiler that does that. What Rust does do extremely well is that the errors are very clearly explained in the terminal (with an error code that has pretty good online documentation), but also gives you hints on simple ways to potentially alter the code to fix it. As a beginner, I've found it way quicker to fix those bugs (even within macros) than the compiler issues I encountered learning C++ - granted back then compilers were a lot worse on that front, but even these days with C++ I've struggled fighting the compiler/preprocessor.
Also, the Rust compiler appears pretty vibrant with lots of improvements being made to help with user friendliness, so it's possible that further improvements in inference might reduce the problem spots (it's already pretty magic to me).
I've started learning Rust a few weeks ago & those are my impressions so far.
>You make some good points but I think it's incorrect to compare Rust & Go. Rust is a systems programming language. It competes with C/C++ more than other high-level languages. In fact, while Go was originally positioned as a systems language but it ended up attracting people from scripting languages like Python because its performance characteristics put it there. You'd probably never bother building a serious web browser in Go, but you would (& Mozilla is) in Rust.
I'm only comparing Rust and Go where they overlap. For example, Rust webservers versus Go webservers. There are other languages that may overlap different parts of Rust, such as in fact, Ruby and Python.
Go and Rust are both more general than the languages people compare them to. Both have C interop of various levels. Both allow unsafe code that touches memory directly. Both are high performance, relatively low level, and both provide some level of memory safety (though, Go provides much less.) I think they overlap a whole lot more than people think.
>In terms of correctness, I've never heard claims about improving security issues, except in so far as those caused by memory/concurrency - think of it as necessary but not sufficient for security. This "limited" class of bugs is responsible for quite a large number of runtime issues & they can be frustratingly difficult to find/fix (+ be confident that you did actually fix it). It's hard to say how much better things will play out in real-world software development as at some fundamental point there are always unsafe calls which weakens the guarantees Rust can make (but does put an explicit boundary on where you should go looking for bugs).
Well, I personally would claim it helps a lot of security and crash issues. Buffer overflows, use-after-frees, race conditions, and more. Also, most people do not overstate the safety of Rust, but beginners frequently misunderstand it. This is because people often list the benefits without listing the caveats.
> As for compiler errors not helping you understand the problem, I have yet to encounter a compiler that does that.
Not very mainstream, but take a loot at elm's compiler errors [1]. They worked hard in this direction, and the result is both helpful and beautiful.
You can try loading up any of the examples in the online editor [2] and introducing a random bug, just to see how the compiler <del>barks</del> tries to gently teach you.
I think my point was that they may teach you if you already have some fundamental knowledge (or help remind you of the rules anyway) but they're not instructive in and of themselves (you could copy-paste the suggestion to "fix" your problem quickly but that's not really increasing your understanding I think).
Rust has a strong policy of adding tests when adding/changing code, so almost all error messages are tested in some form, including "UI" tests, that check the exact formatting.
>In terms of correctness, I've never heard claims about improving security issues, except in so far as those caused by memory/concurrency - think of it as necessary but not sufficient for security.
Huh? Those might not be sufficient, but are the source of 99% of security issues.
Spectre & meltdown would like to have a word with you. Yes, memory/concurrency are a large class of problems for C/C++, but plenty of security issues still exist in other languages (check out the CVE count for Django for instance). The thing about security is that attackers will predominantly use the path of least resistance. As prevention evolves so does the sophistication & vector of attacks (e.g. timing attacks attack high-level implementation details rather than buffer overflows or algorithmic flaws). Do you have any supporting evidence for your 99% claim? That seems vastly overstated.
Those 2 bugs are part of the class of timing attacks which are quite common for security & no amount of memory safety will help you there (as Spectre & meltdown have shown they're not even restricted to the SW domain). Same goes for things like not sanitizing inputs for things like SQL injection, XSS etc. AFAIK Rust doesn't do much on that front either. I don't disagree that memory related errors are the cause of a lot of problems. However, I think that's because C/C++ is so common & it's such low-hanging fruit, why bother? I see no evidence that attackers are running out of tricks to pull to exploit SW regardless of the language it's written in or the countermeasures you have deployed.
> In terms of correctness, I've never heard claims about improving security issues
I would argue that the rest of your paragraph talks about how Rust (indirectly) improves security issues.
> As for compiler errors not helping you understand the problem, I have yet to encounter a compiler that does that.
Try misplacing a { in an average LaTeX document. But don't say that you haven't been warned. ;)
Alternatively, write some C++ code that uses std::map<std::string, std::string> or something like that incorrectly, and marvel at the page-long exceptions with all the default template arguments expanded into an unreadable mess.
> Also, the Rust compiler appears pretty vibrant with lots of improvements being made to help with user friendliness, so it's possible that further improvements in inference might reduce the problem spots (it's already pretty magic to me).
I also recently got into Rust (coming from Go), and the thing I miss most is `gofmt`. Is there a standard tool-enforced coding style for Rust that the community agrees on, in the same way that the Go community has by and large agreed on gofmt?
> I would argue that the rest of your paragraph talks about how Rust (indirectly) improves security issues.
I think perhaps I didn't communicate my meaning clearly enough. Rust does significantly reduce the risk of a certain class of security problems (reduce not eliminate since it's highly unlikely you'll have 0 unsafe{} blocks anywhere in your dependency chain). That's not disputable since that's part of the language design. That's certainly an advantage it has over C/C++. However, security is far more than just memory safety & I have read nowhere that writing more secure code is a design goal for Rust (I'm not even sure yet such a thing is possible).
> Try misplacing a { in an average LaTeX document. But don't say that you haven't been warned. ;)
> Alternatively, write some C++ code that uses std::map<std::string, std::string> or something like that incorrectly, and marvel at the page-long exceptions with all the default template arguments expanded into an unreadable mess.
I agree 100%. I think perhaps you misread what I wrote? I said I have not encountered a compiler where the errors helps you understand a language.
> Is there a standard tool-enforced coding style for Rust that the community agrees on, in the same way that the Go community has by and large agreed on gofmt?
> Is there a standard tool-enforced coding style for Rust that the community agrees on, in the same way that the Go community has by and large agreed on gofmt?
Ugh, C++ template errors, the bane of my existence (and why I personally avoid using anything beyond dead-simple ones unless I have to interface with STL).
Only thing worse are Java generics, if only because they literally tried to tack them in 10 (heck, getting close to 15 years now) years ago and we are still feeling the consequences of those design choices today, (unless Java 8+ made major fixes to this. Haven't used it in enough detail to make a judgement).
template errors blurb a big wall of text, but if you know how to read it it only takes a few second to find in your code where your problem is. You generally just have to look at the "required from here" (in clang and gcc at least) message which points to your own code 9 times out of ten.
> I think that we could find ways to make equally memory-safe languages that go about enforcing safety in entirely different manners than with ownership and lifetime semantics.
Of course, but at the cost of requiring a garbage collector. Mandatory GC has three main drawbacks:
* It makes it harder and much less convenient to call libraries in this language from other languages.
* Low-resource embedded systems are a no-go.
* GC is typically a tradeoff between speed and memory usage. Manual memory management can be very fast and very lean.
> There are Java and Oberon implementations for single digit MB, like Cortex-M4.
So? Just because you can use a language on a given system doesn't mean that you should use it in any serious context.
A GC typically makes memory usage and execution latency non-deterministic, or at the least very hard to analyze. If your washing machine software OOMs whenever the GC didn't run between two button pushes, you'll have a great Heisenbug.
"PERC Ultra offered Lockheed Martin the responsiveness it needed to meet its most demanding timing requirements. In addition to real-time threading and deterministic garbage collection, PERC Ultra provided the instrumentation and VM management tools necessary to support the mission-critical real-time requirements of the Aegis Weapon System."
"The Lockheed Martin-developed Aegis Weapon System is the sea-based element of the U.S. Ballistic Missile Defense System. The Aegis Weapon System is a radar and missile system integrated with its own command and control system, capable of simultaneous operation defending against advanced air, surface, and subsurface threats."
Is this where we bring in the anecdote about the missile flight control system which never freed any memory, because the minumum time to OOM was less than the maximum flight time of the missile?
> A GC typically makes memory usage and execution latency non-deterministic, or at the least very hard to analyze.
You could say same about malloc and free. Not deterministic at all either. Memory fragmentation is a huge issue as well, and can bring down the whole system.
Typical GC (not all flavors) has a huge advantage on microcontrollers: you gain ability to compact heap. No more fragmentation.
That said, mostly I try not to dynamically allocate anything in firmware or kernel drivers. Whenever possible. Sometimes I write my own specialized allocators. For example a simple wait free allocator fast and rugged enough to call from an interrupt service routine.
Thanks for pointing it out, I wasn't aware of it and it does look good, but they clearly state the uses cases which it isn't fully ANSI C compliant on page 24 of the documentation.
That's close enough in my book. Those MCUs are tricky targets and I can certainly live for example with not being able to pass structs as return values. Or without re-entrancy. Perfectly understandable once you take into account limited IRAM space, 128 or 256 bytes, where stack, register banks and most of your temporaries and globals need to reside.
"Less than absolutely total standard compliance" != "a significant challenge compared to absolutely total standard compliance", especially if the differences are clearly documented.
The challenge is not being able to write idiomatic ANSI C, rather trying to tame the compiler to produce code comparable to hand tuned Assembly to fit into those processors, the majority of time using compiler specific extensions.
You're sounding like a propagandist - like you want to just argue your talking points, rather than actually have a conversation where you listen to what the other side is actually saying. It makes you a real pain to talk to.
Just in case you're actually trying to engage in good faith, though, I'll try this one more time. If I have a compiler that is less-than-100% standards compliant, I may not be able to use a few features of the standard. That means there may be a few ANSI C idioms that I can't use. Of those, the number that I would choose to use on that size of processor is very, very few. So in practice, there is no "challenge".
Why would I use very few of these features? Because on a processor that size, you're not writing a huge app. You don't use all the functions in the standard library, you don't use all the keywords, you usually don't push the language very far at all. At worst, you might have to develop one or two idioms of your own. It's... mildly annoying, rather than the big deal you're trying to make it.
Yeah not being able to write idiomatic ANSI C, rather trying to convince the compiler to produce code comparable to hand tuned Assembly to fit into those processors, the majority of time using compiler specific extensions.
I wrote smaller programs with it for a Z80 home computer which originally didn't have C support. I didn't encounter bugs, but it's true that the code generation is less than optimal for Z80 when compared to manually written assembly (understandable because of the small register set), but the compiler supports mixing assembly and C very well, so no complains here :)
Literally the whole industry doesn't care two bits about "100% ANSI C compliance", which I'd guess isn't even possible on a pure Harvard architecture like AVR or PIC.
Even the ESP8266, where ram & flash are measured in kilobytes can run a minimal version of python. This whole "low resource can't run heavy languages" trope needs to die.
So, ... low resource can't run heavy languages fast (yet?).
Good thing is by using MicroPython and writing the time critical stuff in C you can get the best of both worlds while paying the least.
When you're working in a GCed language you assume your language's GC is responsible for freeing memory. When you interoperate with a language where owners are responsible for freeing memory, you have to have a way to "disown" structures you've created but passed into the ownership language (e.g. a callback you've passed to a library function) so that your GC doesn't free them, and a way to "own" structures you've received from the ownership language (e.g. values returned from library functions) so that your GC does free them, and neither of these things will be easy/natural in your language.
When you interoperate with another GCed language it's even harder, virtually impossible, because both languages' GCs assume they own everything and so anything that's visible in both languages will be freed twice.
I've done it in Java; it's not officially supported in the language standard (or has only recently been added if so - certainly they were talking about it for years), and the wider language does not generally have the support or idioms you would want (e.g. try-with-resources was only introduced a couple of versions ago), libraries aren't oriented towards that style.... It's certainly doable but I'd stand by it not being easy or natural.
Because you have to add support for GCing of foreign structures from these libraries, or keep the interface low-level and force the user of the GCed language to manually manage these foreign objects. Adding support for GC can be difficult if not impossible, because GCs often arrange memory in special ways (different pools, etc.) whereas the foreign library probably just uses malloc.
What I meant was: If language X needs a GC, then writing a library in X makes it difficult and very inconvenient to use that library from another language.
The comments of jonathanstrange and humanrebar are also spot-on.
As an occasional Ada programmer, I've taken a look several times at Rust and have decided to skip it every time. Ada might be a pain in the ass sometimes (alias rules...), but it's way easier than Rust. In my opinion Rust is a classical case of technology that gets into humans' way rather than serving humans. For me it's just not worth the hassle, especially since most of my programs do not require any soft realtime performance guarantees and therefore work well with way more convenient garbage collected languages like Go, CommonLisp, and Racket.
That being said, Rust is already so obscure that it can easily replace C++ and I predict it a great future. Programmers love obscure programming languages with steep, long learning curves that allow them to show off.
Second the Ada note. That language is so well constructed and thought out on many levels.
...except the outer, most superficial level. I'm genuinely afraid that it will never "catch on" because it just looks weird. (But not weird enough to attract that kind of people.)
It's a shame because
- Ada generic packages are exactly what C++ templates should have been
- Derived types and record extension is inheritance that makes sense
- Access types are obvious in hindsight
- Correct terminology for procedures and functions does help a lot in communication
- RAII in the shape of controlled types feels a bit bolted on but it works very well
- Named blocks and explicit closing is easy but very useful
- Class-wide types are deemphasized the way polymorphism should be
- The -gnatyy flag is almost as good as gofmt.
- Tasks and protected types form a very intuitive and safe concurrency mechanism
- Having contract-based programming as an option built into the language is way superior to relying on asserts in the procedure
- While not always budgeted for, when there is time to spend, SPARK is uh-mazing. Strong guarantees at relatively low cost, and reasonably easy to learn as well.
The thing that kept me from trying Ada (back a couple of years ago when I was writing firmware and so was kind of on its home turf) was a lack of good resources for learning it. I got a copy of the book "Programming in Ada" by Barnes, and did not find it very helpful. Often the advice seems to be to go read the Reference Manual, which I agree is highly readable for a language standard, but it's not aimed at users of the language. If anyone here has any other resources to recommend, I'd be happy to hear about them.
Definitely! My C coding improved vastly after spending some time with Ada. Though it's worth keeping in mind that it's not only about each individual thing being good – it's also that they work really well together.
Meaning if Stepanov had not played with Ada for its first implementation, followed by Bjarne advocating him to use C++ instead, the STL would never happened in its form.
And yes, it wasn't quite the STL, we had quite a few variations of it, the most well known coming from SGI, until things kind of settled at ANSI.
I disagree. Stepanov wanted to write that kind of software. He would have done it in any vehicle he found suitable. If he hadn't started with Ada, he still would have wound up writing it in some language.
And C++ was among the better candidates for the language to use. It was more suitable than Ada.
And, do you have any basis for the statement that Stroustrup advocated C++ to Stepanov?
"And, of course, Andy and Bjarne Stroustrup are responsible for putting STL into the standard."
"The support of Bjarne Stroustrup was crucial. Bjarne really wanted STL in the standard and if Bjarne wants something, he gets it. He is as stubborn as a mule. He even forced me to make changes in STL that I would never make for anybody else - I am also stubborn, but he is the most single minded person I know. He gets things done. It took him a while to understand what STL was all about, but when he did, he was prepared to push it through. He also contributed to STL by standing up for the view that more than one way of programming was valid - against no end of flak and hype for more than a decade, and pursuing a combination of flexibility, efficiency, overloading, and type-safety in templates that made STL possible. I would like to state quite clearly that Bjarne is the preeminent language designer of my generation."
I got the names mixed up, it was Andrew Koenig not Bjarne.
"My attempts to implement algorithms that work on any sequential structure (both lists and arrays) failed because of the state of Ada compilers at the time."
"In 1987 at Bell Labs Andy Koenig taught me the semantics of
C. The abstract machine behind C was a revelation. I also read lots of UNIX and Plan 9 code: Ken Thompson’s and Rob Pike’s programming style certainly influenced STL. In any case, in 1987 C++ was not ready for STL and I had to move on. "
"In 1993, after 5 years working on unrelated projects, I returned to generic programming. Andy Koenig suggested that I write a proposal for including my library into the
C++ standard, Bjarne Stroustrup enthusiastically endorsed the proposal and in less than a year STL was accepted into the standard. STL is the result of 20 years of thinking but of less than 2 years of funding."
> That being said, Rust is already so obscure that it can easily replace C++
An obtuse syntax is a big downside of C++. Implying that a steep learning curve and the ability to 'show off' helps keep the language where it is grossly misrepresents the vast majority of C++ programmers. Those traits were born out of necessity. In C++98 you simply had to be 'clever' to keep up with modern languages because the language was stagnant for a decade. If the committee can't add things, you do something clever and do it yourself.
C++ was stagnant for the dot-com era, Web 2.0, and the rise of mobile apps, and today it thrives. There's more to that than being entrenched in legacy code bases.
Rust emulating C++s difficulty as a way to cultivate an elitist community, if that's what you're implying, will not work now when C++ is moving in the opposite direction.
I've taken a brief look at Ada several times, but gave up on it because it seemed difficult to get a cohesive set of documentation and examples (and ideally a good book) that were all in sync. Since Ada has been around for so long, there's an awful lot of outdated material. Do you have any suggestions on materials one should use while learning Ada for hobbyist purposes? I'd like to give it another go.
Four versions of Ada, named after the year it was released: 83, 95, 2005, and 2012. Each new version adds features on top of the previous.
Ada 83 has
- arrays,
- records (structs),
- derived types (subtypes carrying the same data as parent type),
- subtypes,
- access types (thick pointers),
- procedures (subprograms executed for their side effects) and functions (subprograms that return values),
- Named parameters
- reference arguments (greatly reducing the amount of pointers you have to deal with)
- Default values for arguments
- overloading by type
- in/out parameters
- private package parts (encapsulation)
- generic packages (kinda like C++ templates)
- exceptions
- tasks (concurrent processes that communicate with message passing)
-----
Ada 95 adds a lot of things that make it easier to do Java-style OOP:
- Record extension (inheritance; subtypes carrying additional data their parent type does not)
- Dynamic dispatch of subtypes
- Abstract types
- Subprogram access types (function pointers)
- Sophisticated package hierarchy (though potentially somewhat unintuitive: child packages extend their parents, parent packages are not umbrellas for children the way they are in Python)
- Protected types (protected objects)
- Modular types (unsigned ints with defined overflow characteristics)
- Unbounded_String (the std::string of Ada, compared to the char[] situation in Ada 83)
-----
In Ada 2005, the greatest addition is probably the Collections packages, which added several common data structures to the standard library:
- Instance.Method(Param) syntax sugar for Object.Method(Instance, Param)
Ada is another non-C with odd semantics, so the problem is that it goes too far and not far enough: It's not enough like C to be familiar, but it's not far enough from C to be able to go head-to-head with Haskell. If I want something that's very safe and don't care about it being similar to C, I'm going all the way to Haskell and not bothering with half-measures.
But it's a big deal to say, "Whenever safety matters, you alwys have room for a runtime with a garbage collector." I feel like you hven't substatiated that claim.
> I would normally define 'correctness' to include rigorous mathematical proofs.
Do you have an example of a language that does what you're looking for?
On the proof side, Rust's built in unit testing is great, and allows for quick validation of code (proofs). But I think you mean something different.
> Go works wonderfully
Go does work wonderfully, you should definitely use what you like. For me personally, though, Go never excited me. Rust on the other hand continues to be exciting, I always feel like I'm learning something new (and I've been using it for the past 3 years).
> For example, you could always just clone memory at every occasion, return the input instead of borrowing, etc. In fact, these things might be easier for a beginner to do.
I think most Rust beginners (I know it was true of me anyway) do often just clone everywhere. Eventually, you then replace that with Rc or Arc... And then one day you decide you want the fastest thing on the block, and now you know the in's and out's of Rust, so you decide to up the ante and put lifetimes on everything.
> Do you have an example of a language that does what you're looking for?
Dependently typed programming languages such as Coq and Idris let you write proofs about your programs. These languages are fairly academic, and most engineers probably won't find it worthwhile to use these tools. Personally, I love them. I wrote a short article about my experience with Coq: https://www.stephanboyer.com/post/134/my-unusual-hobby
Rust has the same level of rigour, other than lacking totality. Not having dependent types makes properties a lot more cumbersome and less practical to encode, but the actual proofs you get of the properties you do model are just as valid as those in Coq and Idris (modulo nontermination).
I think they mean something like what proof assistants like Cog and Irdis do. ATS is another language that enables proofs, and it's more geared to the systems programming use case, as it's similar to C.
Note that unit tests prove only that the code works for those inputs and those code paths tested. Mathematical proofs are supposed to be exhaustive.
Sorry this comment is so late; ATS is also interesting because it has linear types that allow compile-time resource tracking similar to Rust's ownership and lifetime tracking. If you use linear types you can opt out of the garbage collector. That makes it very attractive for systems programming, and in particular embedded systems. I believe there is a demo of ATS running on an Arduino of some sort.
I haven't had the time to learn it yet, though I would like to.
You'd have to look at niche languages like Coq, Idris, or Agda if you want mathematical proofs. There's a lot of research that needs to be done before proven programs can become the norm.
> There's a lot of research that needs to be done before proven programs can become the norm.
At least equally importantly, we need a new generation of developers to grow up with these tools before they can become the norm.
In fact, the biggest contribution that academia could make to safe programs is to replace Java and C by Rust in all the programming courses, so that the next generation of developers is raised on Rust and makes that language (and its strictness mindset) popular in the industry in the same way that Java's relevance in the industry is based (in part) on its prevalence in curriculums.
As Zalastax was saying, by the standards of Coq, Rust doesn't have a strict mindset.
Rust isn't the first language to emphasise correctness. We've had Ada for decades, but it's not taken over the world.
Going to the extreme, full-bore formal methods will never be taught as introductory material on programming courses for the masses, but they will continue to be taught at good universities.
I'm not sure this is a bad thing. For most applications, ad-hoc develop-and-test makes good sense. RAD is important in some domains. Both formal methods and highly strict languages have their downsides.
There's more to correctness than language, of course. A shift toward correctness could be as simple as encouraging students to put runtime asserts in their Java code.
I hope we'll see more advanced type features added to languages. I find it very annoying when I can't be expressive enough and have to resort to comments and runtime errors. It's a very fine line to walk though. TypeScript is the language that I think is best at walking that line currently.
> On the proof side, Rust's built in unit testing is great, and allows for quick validation of code (proofs). But I think you mean something different.
Unit testing can only proof one instance of the input domain, e.g. the function square() returns 4 under the input 2. Languages like Coq allow you to proof that the function square returns the squared input for every possible input.
And for anything more or less complex and optimised (e.g. Egalitarian Paxos), you'll not only end up with proving the correctness of the algorithm, but also the correctness of the implementation (which by themselves will hugely vary).
I see way more future in one's ability to write proofs of correctness in the comments before the function definition in any language; rather than preferring any specific language for the sake of proof.
C# Code Contracts can specify constraints (like range check, nullability, list sizes, etc) on inputs/outputs of functions and enforces these statically based on the code inside the function.
> but I would normally define 'correctness' to include rigorous mathematical proofs
Well, Rust does push for strong algebraic types. They aren't as expressive as other languages (the lack of higher order types irks me all the time - no, hygienic macros are not a good alternative) but they are the best ones you will find on any bare-metal language.
I agree with everything, just wanted to nuance with a little thought - if cloning memory at every occasion makes the code safer and more correct but slower, that might be worth it. Always favor correct over broken. If you are coming from C to Rust in a domain that is actually better suited for Go... well at least you are arguably better off with Rust than C, right?
So I think that Rust will advance the overall state of the art, just a bit.
Oh yes, as long as the anti-patterns that emerge don't cause other bugs, they're OK; but some may lead to logical bugs. I'm not sure how likely that is, though- not enough history to go off of yet.
Rust is certainly a step in the right direction. I hope the toolchain and so forth end up in a state where all forms of users can love it, though; I have seem some reasonable opposition to including Rust in kernel code, and that sucks.
Regarding your first point, I have never seen anyone on the Rust side of things claim that Rust can solve most correctness bugs. The attitude has always been to take things one step at a time, and to slowly strengthen the type system to make it expressive enough to write more statically enforceable constraints.
"Most" is a problematic word because it requires a baseline to compare against. When you're coming from something like Ruby, Rust most definitely solves "most" correctness bugs. When you come from Go, probably not.
* That Rust is only harder because it enforces 'correctness.' It certainly is harder because it enforces correctness, but it's also harder because of how. I'm not saying there's a better approach to this, but I think a lot of people are implying that there isn't, and I don't think that's a safe assumption. I think that we could find ways to make equally memory-safe languages that go about enforcing safety in entirely different manners than with ownership and lifetime semantics.
* In fact, the entire idea that Rust enforces correctness. Only if your definition of 'correctness' to be memory-safety, but I would normally define 'correctness' to include rigorous mathematical proofs. Rust's safety guarantees are often accidentally blown out of proportion; they mainly aid in preventing security and concurrency bugs, but only a certain class of each. This is still useful, but this caveat really needs to be in your face more often, as a lot of people will not mention it when touting the benefits of Rust, and beginners can get easily confused about what exactly Rust prevents you from doing.
* The idea that Rust's approach is always worth the trade-offs. Go is another programming language I like, and there are definitely things that are simply easier to write in Go with few disadvantages. Fearless concurrency is a wonderful feature, but for embarrassingly parallel problems like, often, web servers, where each thread is usually independent in terms of mutable state, Go works wonderfully. It also lets you shoot yourself in the foot in a way that Rust wouldn't, but often for a lot of simpler apps it still ends up being easier.
* The idea that solving the compiler errors makes you understand the problems correctly. For example, you could always just clone memory at every occasion, return the input instead of borrowing, etc. In fact, these things might be easier for a beginner to do. There will probably be a ton of Rust anti-patterns that come about from trying to resolve compiler errors.