Regarding the main example of author, it's great that the progressive side of Go team won, so generics are coming in Go 2.
Regarding the main example of author, it's great that the progressive side of Go team won, so generics are coming in Go 2.
Java, C# and C++ control it via compiler flags, and also have the ABI issue across language versions.
I like rust's syntax. It has very elegant macro syntax, expressions are values, and all that other good stuff.
There are some small parts of it that are ugly, but honestly Java and C++ are far uglier. Templates in c++ are awful. Java's wildcard syntax and lack of macros is ugly.
I find rust much prettier syntax-wise than most statically compiled languages. Prettier than Go or Java or c++.
> In terms of ease of getting started and using tooling, it really can't ever compete with the corporate languages like Go, Swift, Kotlin
Why not? I don't see any reason why it can't compete. Heck, it's a pretty corporate language too in terms of its mozilla backing.
That being said, people constantly talk about how easy it is to get started with python, and python has even less corporate backing than rust. That point makes no sense to me.
> C++ had the same safety characteristics for at least a decade with dozens of linters and sanitizers on the market
C++ does not, and did not, have safety features like rust's. C++ cannot guarantee ownership semantics are correct. It cannot identify data-races at compile-time. Leaving dangling references in C++ is trivial.
For example:
std::vector<int> v = {1};
auto ref = &v[0];
v.push_back(10);
std::cout << *ref;
That code compiles in c++ even though 'ref' may be invalid because 'v.push_back' may have reallocated the underlying memory. I don't know of any compile-time tooling for c++ that reliably catches that bug. It's a clear memory safety issue, and it's basically the exact thing rust's ownership system saves you from.In short, I don't think you have a complete understanding of what rust's benefits are nor why it's innovative.
It's niche, been around for over a decade, still hasn't reached critical mass. Golang did. Numerous mainstream projects are in Go that drive entire industries now e.g. docker, k8s. Rust's biggest deployed code is the CSS renderer of a web browser with single digit market share. I'm not saying Golang is better on PL theoretic grounds (obviously it's worse), but it proves what corporate marketing can do, which we have known since the Sun era. Also Python was not that popular until the same corporation behind Golang decided to adopt it and hired Guido.
C++ compilers don't catch all the errors, but static analyzers do. Taking your code snippet as an example:
% cppcheck ./main.cxx
Checking main.cxx ...
main.cxx:8:16: error: Using pointer to local variable 'v' that may be invalid. [invalidContainer]
std::cout << *ref;
^
main.cxx:6:13: note: Address of variable taken here.
auto ref = &v [0];
^
main.cxx:7:2: note: After calling 'push_back', iterators or references to the container's data may be invalid .
v.push_back (10);
^
main.cxx:5:19: note: Variable created here.
std::vector<int> v = {1};
^
There are proprietary static analyzers that go much deeper and catch less obvious bugs. Even without paying, you have ASan, TSan, UBSan, valgrind, afl, among others to do runtime analysis or fuzzing to catch memory errors and data races. Unfortunately these aren't used as much as they should be. Maybe those programmers have a point. In practice, memory management is not remotely the most difficult or bug-prone part of software. If you are using C++11 or later, the standard practice is to never do raw new and delete. std::move(std::unique_ptr...) has the same benefits as Rust borrowing without the borrow checker (although it is not rocket science to detect use-after-move with analyzers). Arc <-> shared_ptr. Sure, Rust's versions are better, but let's not get carried away, it's <10x better than C++ (with 1/10000x the ecosystem, jobs, books, libraries, people).Rust 1.0 happened in 2015; everything before that was incompatible with other releases, sometimes multiple times per day.
<source>:8:18: warning: dereferencing a dangling pointer [-Wlifetime]
std::cout << *ref; // D: error – because pset(p) contains {invalid}
^~~~
<source>:7:5: note: modified here
v.push_back(10);
^
https://godbolt.org/z/wbRghNRust biggest contribution has been to force other languages to up their game in memory security.
Genuine question.
The borrow checker in rust has no runtime overhead (whereas go's race flag is runtime only, and has so much overhead you can't turn it on in prod). In addition, the borrow checker catches data races, but it also catches more subtle ownership issues too!
For one example of an ownership issue which isn't also a concurrent data-race, let's look at something in go that the -race flag doesn't catch, but that the borrowchecker in rust does:
// go
b := []byte("foo bar")
foo := b[0:3]
fmt.Println(string(foo))
b = b[:0]
b = append(b, []byte("not foo")...)
fmt.Println(string(foo))
// prints "not"
// This is because 'foo' is actually "borrowing"
// bytes from b, but go doesn't have any way to indicate
// that said "borrow" is active, so writing to it might be a bug.
That code is here: https://play.golang.org/p/0EK2OzeUG0vNow let's try to write code that has that same bug in rust:
let mut b = "foo bar".as_bytes().to_vec();
let foo = &b[0..3];
println!("{}", String::from_utf8_lossy(foo));
b.truncate(0);
b.append(&mut "not foo".as_bytes().to_vec());
println!("{}", String::from_utf8_lossy(foo));
That code is here: https://play.rust-lang.org/?version=stable&mode=debug&editio...If you try to compile it, you'll see that it doesn't compile with the following error: "error[E0502]: cannot borrow `b` as mutable because it is also borrowed as immutable"
In the rust code, the borrow checker keeps track of the fact that "foo" is currently a reference into "b", and because of that, it knows that it shouldn't let the programmer modify the memory out from under "foo".
This is just one of many examples I could give, but hopefully it provides some explanation of how the borrow checker is more flexible than just race detection.
Basically, there's a lot of cases in programming when it's useful to have a function which takes ownership of an object and turns it into something else, or borrows an object temporarily but then gives back ownership. In languages like go, the fact that "close(channel)" means that the channel can't be written to again is a runtime guarantee. In rust, it can be expressed to the compiler that 'channel.close()' means that channel is gone and using it again is a compile time error. Ownership ends up being a really common concern, and the borrow checker handles it nicely.