23:41 "Rust Runtime crashed when I introduced ridiculous levels of parallelism" This is because std::thread is not the same as tokio::task or Go's go. std::thread actually creates an operating system thread through the OS's native interfaces, and the operating system can impose a limit to the number of threads that your program can spin up. It has that overhead of the OS needing to initialize structures and put it in the scheduler. tokio::task and go, meanwhile, are greenthreads. These are fake threads that are scheduled by the tokio or go runtime, and executed in a pool of working native OS threads. OS threads are better for getting the most that you can out of your computational and memory resources. Greenthreads are better for I/O heavy tasks where, most of the time, the threads are sleeping while they wait on networking or filesystem operations.
14:40 this is not how to iterate over a string (or the characters of a string) in rust, and it's not a naive solution either IMO .chars().nth() cannot be compared to indexing as it does significantly more work. strings in rust are utf8 encoded. they are a wrapper over an array of u8s, where a character may be multiple bytes wide (i.e. 🦀 becomes [240, 159, 166, 128]). because of that, you can't index into the string and expect to get a char out of it. at least not in O(1) time. if you do want to index into the raw bytes and get a single byte (not a character) at a specific index (which is what the code does), one can use str.as_bytes()[i]. this is what Go does when you index into a string. "🦀"[0] returns "ð", not the real emoji. "🦀".chars().next() returns the emoji. when you do .chars(), an iterator over the characters is created, and every time you call `.next()` on the character iterator (which `nth()` does), it walks the array of u8s as many bytes as it has to to decode a full character. `nth()` continuously calls `next()` on the iterator as many times as it has to yield the character at a given position. this means that creating an iterator and calling nth *on every iteration* means walking the string from the start of the string, up to the character index every time. you effectively have to decode the utf8 string on every iteration up to character index `i`. the solution is to create one iterator outside of the loop and just iterating over it, so you don't start decoding the utf8 string at the very beginning of the string on every iteration, but where you left off in the previous iteration. if you were to compare this to equivalent Go code, you would have to add a for loop that does nothing but iterate from 0 to i before getting the character inside of the loop. also, iterators often eliminate boundary checks, so thats another reason to prefer iterators over manual for loops again 21:05 and 27:48 mentions how strings in Rust are inferior, which isn't true. the code that was shown was not a naive solution that someone knowing utf8 would implement. other languages either encode strings differently, such as javascript where strings are UTF16, in which case string indexing just works, or it's a hidden implementation detail and indexing is O(n), *or* indexing just returns the raw byte at a given index (like go). 27:18 rust is data-race free, but not race condition free. race conditions aren't memory errors, and they are safe.
Give it to him! I was wondering myself what he is talking about. Strings are pretty well handled in Rust. Well IF you use the String struct and not raw u8 arrays.
I wish I could upvote this comment more than once: the reason the "naive" rust version is slow compared to the Go code is because they are doing completely different things.
Looking at his code, you can see that he really doesn't know either language very well. And simply mapping his C and C++ knowledge to both languages. Not even sure why he's casting the byte to a rune in Go. In Rust, beyond using as_bytes(), it would have been better to use windows() to do the sliding window rather than coding it manually.
In all fairness, it is "naive" what he's doing. The problem with these languages, and also C++, is that they layer a bunch of opaque abstractions, and so hide some pitfalls. In C, the naive solutions that an undergrad may write are usually just as fast. In fact, the biggest issues with these languages can be summed up in one word: complexity.
Nice comparison, but I still think that I would strongly consider Go. If I were starting a company today (I did in 2000, but that was before Go or Rust; we used Java), I would also have to consider how difficult it would be to hire good developers. If my project was written in Rust, I would need to find developers who were already competent in the language. If I chose Go, I could probably hire people who were already expert programmers in any language, even if they did not know Go. I could give them a copy of a good Go book (The Go Programming Language, by Donovan and Kernighan, maybe, or one of the newer excellent books) and know that the new developer could learn the language in few days.
1. To start project in go just say "go mod init ". 2. There's good way for project structure not wrong way except files in one folder is package have to same pkg name and all your packages in module. 3. Testing is more then unit testing in go you can make benchmarks, code m coverage, race detection, profiling and much more with only command line flags. 4. Unused variable and packages also non implemented functions increases compilation time. 5. Note: Concurrency is different the parallelism and go implemented both the examples you've used in this presentation is more like parallel version. To use goroutine in jus type go in front of the function if it's returning values or talking to other goroutine use channels not wait groups.
It feels like the point of Rust's compiler was missed here... yeah, it's phenomenally slow, because a point that's regularly emphasised with it, is that it's running slews of compile-time checks for things like memory errors you'd encounter in other languages, to the extent you'd might be able to call it something like "Compiler Oriented Programming"...
Because rust trade off is the compiler speed. And that is what makes rust good because it does all the work in the compiler time and ensures max runtime speed and safety
You might've heard that there's nothing without trade-offs in Computer Science. And when a language says there's no compromise, there's probably a hidden asterisk. For Rust, this is it. Yes, you choose all 3 (reliability, correctness, performance) at runtime, but the secret trade-off is development time.
@@VivekYadav-ds8oz I wouldn’t say “dev time” is is necessarily sacrificed with rust, mostly because I don’t think compiler performance is super critical for dev productivity. I would know, because I work a lot in Typescript with Webpack, and 90% of my dev tools are written in JS/TS.
@@dupersuper1000 I have to disagree, compiling time is quite important, I mean half the reason go was created was to have better compiling times, and that is a completely valid reason for choosing a language over another, if you don't mind spending a bunch of your time waiting for your program to compile then you can reach for rust to gain other things with the cost of additional compiling time
While Rusts Strings can be a bit harder to work with, they are at least 100% correct. It forces you to handle the Utf-8 correctly, which can be a bit annoying for simple scripts, but is better for bigger projects.
From both the Rust and Go code, it's clear that Michael hasn't worked with UTF-8. In the Go code, he operates on bytes of the string, which isn't unicode safe. He does cast a byte to rune multiple time, but I'm not 100% sure why. In Go, if you need random access to a unicode string, then you'd need to copy the string into a rune slice. In Rust, you can gain access to the bytes of the String either with as_bytes() which returns a slice to the internal Vec, or bytes() which is an iterator of the u8. If you need code points, then use chars(), you can always collect into a Vec, but you need to make the choice to allocate the Vec.
Having someone with such an intense background in development do a deep-dive like this is INVALUABLE. Thank you for taking the time to put this presentation together - I hope you give this talk at a few conferences to help spread the info. It was great to listen to.
Just so you know your validate string examples are not an exact comparison. Rust strings are always valid UTF-8. This is not true for go. Go can have valid utf8 but its not enforced by the default string. For your GO code what you do is grab the byte out of the string. Depending on what you wanted the byte could be invalid(if its the second byte of a utf8 character). You then turn it into a rune. I'm pretty sure that does "nothing" as its not going to change it to UTF8. But you can then use it as a "char". In the original rust code you get a UTF8 character out of the string. This requires parsing up to the first 4 bytes. You then do this over and over again. To get the Nth character in utf8 you must parse the previous ones. Basically you were doing more work then you needed to. You could have done something like. If pass.is_ascii(){ Let bytes = pass.as_bytes() Prev = bytes[0] //you know its ascii and can treat it like the go string or more correctly a non null c string. ... Else{// its utf8, you must grab chars not bytes Let prev = pass.chars().nth(0) //could even be replaced with the first next() iterator call. For char in pass.chars().skip(1).take(5){ ... I typed this on my phone so there may be a few errors. Basically the restriction you talk about is less of a rust restriction and more of a utf8 one. A pure GO(c or c++) utf8 string would have the same issue. Hope that all makes sense.
the original rust version, would not fail with utf-8 strings, but be slower. While the go version would fail. An undetected, but correct slow version can be optimized later, but an undetected wrong version, can cause serious trouble in production. I think that correctness is somehow overshadowed by performance (There are verry little public available correcness metrics, but if your program is fast (or not) someone will boast about it (i mean i do that too)). Rust does not compromize on correctness. And theres where it relay shines in my opinion. Yes im totally biased to rust.
@@benmuker6300 Yes, but i think the issue was more that he attributed the slowness/weird api to rust. Where the issue was more that rust uses UTF8 by default where go does not. You would have the same or a similar api for GO Utf8 strings. He just didn't write a fast path for if the string was ascii. He also did probably the worst implementation for checking strings. But none of that was Rust specific only Utf8. I also disagree on his fn vs impl fn argument. As well as his String vs str. Rust chose correctly for both of those cases. It just may not be clear as to why on first glance. But rust is a great language for correctness.
(edit: update to actually be the correct function ^^') This handles utf-8 correctly, and is quite fast ```rust fn validate_extra(password: &str) -> bool { let mut rc = false; if password.len() < 6 { return false; } let mut m = '0'; // Safety: It was earlier checked that the password contains at least 6 characters let mut chars = password.chars(); let mut prev = chars.next().unwrap(); for test in chars { if (test as u16) < (prev as u16) { return false; } if test == prev { if test == m { rc = false; } else if m == '0' || rc == false { rc = true; m = test; } } prev = test; } rc } ```
@@erikfundercarstensen7344 As long as you use an iterator it should be faster. Calling nth is like multiple calls to strlen. Its just not the right thing to do. Also str::len returns byte length not number of chars. For that you should iterate through and take 6 characters then check if next is none. If its not the length was wrong. You should also use an enumerated iterator so at the end you know if the take took 6 characters. They way its done here you could only have 2 characters. One 4 byte and one 2 byte char. I forget the default behaviour of as. If it just trims your as u16 comparison could be wrong as well. Or even if it just sets u16::max it still could be wrong. Utf8 just complicates strings so much its hard to get right and performant.
@FichDich InDemArsch Sure, like most people won't document or do error handling. Maybe a better example would be checking strlen or nullptrs. But we know those things are wrong. The issue is just that UTF is a great standard, but has quarks. It's less of a reflection on rust.
That’s a good talk! I guess Rust compilation times are slow(er) because it does a lot of checks behind the scene (I’m pretty sure you’ve seen Rust compilation errors telling you about dangling references etc, that gives me a feeling of running a static check analyzer and they’re kinda slow), whereas Go just compiles the program as long as it’s syntax errors free, but it leaves you with all the dangers of null pointers, dangling references and so on.
You're probably right with the comment about Rust, but I think there is more going on with Go than just checking syntax. C just checks for syntax as well, but it compiles significantly slower than Go. C syntax is also simpler than Go. So there must be more to it's compile speed. The Go compiler speed is really dark sorcery of some sort.
@@masmullin I’ve found some info. It boils down to Go just being smarter about analyzing what and how to compile, here’s more detailed explanation: talks.golang.org/2012/splash.article#TOC_5.
The string thing about rust was because Rust uses unicode strings by default. They can't be indexed, because chars are not fixed size. The "string.chars().nth(i).unwrap()" actually loops through the entire string up until the character index you requested. This is a significant difference from C, where strings are just arrays of bytes, with ascii encoding assumed. Rust is weirdly pedantic like that in many cases. For example, sorting by float can be pain, because they lack total order property due to NaNs.
Just to add more context. Strings(correct utf8) have to be implemented this way in any language. The float issue is also not rust specific but stated by the iso/IEEE standard. I'll have to double check but i believe there was actually new guidance on this in the latest release.
@@dynfoxx Yes, it's just that rust is one of the few languages that take correctness this seriously. Usually, languages take the simple route of treating strings as array of bytes, and floats as sortable. Treating utf8 and NaNs as special exceptions where, the default code is expected to make no logical sense or fail.
Really appreciate the comprehensive breakdown of the strengths of each language. I'm currently a comp sci student taking C++ courses and both the syntax as well as ease of use of GO has me jealous. Look forward to more videos!
For your slow string implemenation, I think that your Go version should have given you the insight that you're traversing a UTF-8 string. I think that your initial version that was slow converted each UTF-8 character in the string to a UTF-32 (rune) array on every iteration of the loop. Since Go would have given you a copy in a new allocation each iteration, I'd bet a Go version of that same procedure would be slower. Also, Go and Rust syntax are both pretty good. Syntax isn't as much of a human concern as it is a machine concern speed wise, but human understanding is manditory. I believe that they both do a better job than C++ and maybe C. Zig almost looks like Rust because it was already very good.
With the new version of Go supporting Generics, you can argue that Go would be the winner in this debate, because you counted 1 point off because it didn't have generics. Also, you can check for race conditions with the: "go run -race main.go" command It will not compile if you have race conditions, just like rust. So when everything is added up, Go wins by a .5 point margin.
IDK. I have only seen the syntax portion of the video where Michael compares strings but fails to realize that Rust uses utf8 and Go uses ascii. It made me stop watching the video. FYI Rust and Go are meant for different purposes one for RAM memory manipulation and the other for running IO bound services. Rust can work better than Go with some effort but Go can't be a general purpose language like Rust as Rust works on a more fundamental level.
Frankly for me the very fact that they were comfortable holding out on Generics for so long (with that lack afflicting a lot of other design decisions in a significant way) is showing. Rust has held out on async, but frankly that feature is: 1) far more complex (at least if you target as broad as a spectrum as Rust, including small embedded devices) 2) rust solved it quite well and unique with its support for multiple runtime and its postfix await, while go's long awaited solution for generics is... Generic (which isn't necessarily bad, but it makes the long wait all the more annoying) 3) just plain not as important for the core language and with limited broad applicability. It did affect some parts of the ecosystem, but was highly localized, and in the end these designs were fixable. Also, your part regarding race conditions is wrong. First, small nitpick, go run -race will compile code with race conditions, it will just test for them while running, and then error out. And, more importantly, and kindof connected, as I said it will test for race conditions while running, which isn't perfect, as it WILL miss race conditions if they're hidden behind some conditions that are not reached during the run. You can detect memory races in just the same way with any C/C++ program (and many other compiled programs, also Rust for that regard) using valgrind. It's far less comfortable than Go, it's yet another tool etc, but it's not a standout feature that makes Go special, as it was tacked on by Go without designing the core language to be safe, like Rust did. Rusts detection of data races is far less flaky as its based on static analysis (in an ergonomic way due to it being part of the type system), and it's impossible to miss a data race, well, without unsafe that is (where you want to minimize potential, and if you have to use unsafe you can still use valgrind etc. Considering how isolated potential issues are your option space will also be smaller due to localized use of unsafe far with Rust, thus its easier to a sure all conditions are traversed.
Personally, I am very happy with go's insisting on using declared variables and imported modules. Letting that slide makes for problems later. In C, it's #include , in Go, it becomes gratuitous dependencies that really don't exist.
Throwing compilation errors is great for trimming down production code, but terrible for debug builds, which a developer is far more likely to be on the receiving end of compared to production builds.
@@kylek.3689if you use vscode, the golang extention proms you to install go tools like, gopls, goimports, dlv, etc... and with those tools vscode marks unused variables like error and other things too. also you have auto import of packages. and with the command go mod tidy go clean all unused packages from go.mod file (it is similar to package.json but way better). also for debugging go is king, i tried to debug in rust and it was an awful experience.
Hey, I left a few comments that were slightly critical about the languages, but please don't take them as criticism of your video. It's a great video, and I enjoy the subjects of Go, Rust, and programming languages in general. Please make more videos on this subject. It's great fuel to forge ideas about what's best to use, and why.
One aspect is completely missing where Go blows Rust out of the water big times: Ecosystem maturity. IMHO this is THE most important aspect of a language because a language with a young ecosystem will not get you anywhere. Now, Rust's ecosystem is not entirely bad but it's young and especially everything network-related is a mess thanks to async vs non-async vs tokio vs async-std, you name it. So in regards to networking, the ecosystem is actually split which is much worse than simply be unmatured. That's really a problem in the Rust world and currently there is no cure.
True. Go being a "server side" language definitely adds to that. I tried writing a server side application and it's just not there yet because of libraries not being async compatible. I feel like Rust will eventually catch up though and when it does my guess is it's going to be UGE
Strings are not arcane in rust, they are pretty well handled, it's just that what you did is completely wrong. You need to understand that in utf8, you never know wether a character will be 1, 2, 3 or 4 bytes, so getting the nth character means that you will have to go through all the preceding characters as you can't just say that you take the nth element of the bytes array. For example, é takes two bytes, so if you want chars of "héllo", 1st char is h and takes just 1 byte, but 2nd char é takes two, so third char is not the 3rd element of the array but the 4th, that's why the complexity of .chars.nth(i) is O(i), so instead, you should just have iterated through .chars() with a for loop. Then you would have gone through the string without any issue in speed. So you didn't need runes or anything, you just needed plain for c in my_str.chars() { // do something with `c` } which is waaaay simpler than what you did I had a good blogpost about character encoding in the past but lost it. I just googled the thing and that post seems legit, so it might be a good read on the concept medium.com/jspoint/introduction-to-character-encoding-3b9735f265a6#:~:text=A%20character%20encoding%20is%20a,characters%20based%20on%20their%20values. There is also that for string handling in rust stackoverflow.com/questions/22118221/how-do-you-iterate-over-a-string-by-character
I think the community and package manager really make a language. Most devs (outside creative hubs in US) need a language that’s supported on AWS and Azure. While I keep an eye on both rust and go, neither will help me or my clients go more than what I use python and c# for. 90% of jobs in my area need these skills
Great video, but I would suggest you look into ways around the "must be used" variable issue for go. It is a pain in the butt at first but there are ways around it. I would also argue channels in go are a little easier to work with compared to rust. For rust, cargo check is your friend, it lets you ensure code will compile much faster than cargo build, that way you arent waiting for code to compile since rust is slow just to have it fail 80% of the way.
From my experience building a moderately complex to a highly complex system, Go's development time is very less than compared to Rust. Rust sure is fast but it also takes time to develop in it, whereas with Go development time is minimal compared to Rust. It all boils down to simple things Fast development, fast program, mature ecosystem => Go Considerably more time for developing, extremely fast program, memory checks, risk takers ( coz its in early stage and stability is important for long term projects) => Rust
I think rust definitely takes more time to get up and running in when it comes to development time, but once you understand to work with the burrow checker instead of against it the development time becomes comparable with other languages. You will never reach productivity on the levels of python but it becomes comfortable.
Rust itself is fairly stable in its syntax, now with async await being stabilized, and they make a great effort to be stable overall. I will however give you that the ecosystem is still very much in development and many libraries should be considered unstable.
@FichDich InDemArsch I haven't had much issue with lifetimes since I figured them out, and it really helps to use the rust provided structures when dealing with data ( cell, refcell, ect. ) . I can't really say much when it comes to refactoring since in the 2 big projects I've done in rust I haven't had to do yet.
This comment shows he hasn't done any development in Rust. I've actually done a bit of Go at a job and I do Rust in hobby protects. Rust eco system follow the npm model. Claiming Go outdoes that is factually wrong, especially the arguments laid out in your comment. With the eco system literally built into the toolchain, Rust outperforms Go in this regard by an order of magnitude. The question is if the npm model is to prefer or not, that is an entirely different question. I like it, especially for things I do in my spare time, since you literally can get something up and running in a few minutes by pulling in some dependencies.
I have a theory why rust refused to launch your million threads: when using standard rust threads you’re relying on actual Os-threads and when using Tokio I think you’re using green threads
@@user-zd9wd it's also not necessarily green threads(Rust async is a generator and does not grow). Rust async(tokio, mio) just use a work stealing model. You end up with around the same number of threads as your machine has cores. Epol is then used to dispatch events back to async tasks.
After using other languages for a while, and now experimenting with Rust and Go, what are your thoughts on C now? Would you still choose it for any use cases besides the obvious compiler availability ones?
That's a very good question. Short answer is that there are a few areas where I would use C. Most in "non shipping" scenarios (ie testing code). The place I prefer C in a shipping product is bpf code.
It was really nice comparison, very deep analysis of both languages, but it would have been great if you have shown some real time statistics, like when you say rust is at least twice faster. Thanks
Thank you sir. I watched all the way through. I appreciated you showing side by side code. I had already chosen Rust as my next systems level and performance/security oriented language, but I wanted to understand where and when Go might be implicated. Very clear. Even after just two days using Rust and an hour or two on Go your information was clear and relevant. One hopefully constructive comment: on the comparison slides where the bullet points for the each language's score for the given slide topic, the bullet items are often just the inverse of the other language. That's fine and appropriate for the slides themselves, but when presenting you could be more succinct and possibly add additional color or show more code by condensing the spoken delivery to just saying the statements once, as in something like: "Go has a clear and intuitive way of providing an abstraction of treating a string as an array of characters, whereas Rust uses an arcane syntax." You could now use the extra time to expand on that statement rather than repeating it more or less for each language. This is something I was taught 30 years ago by a mentor when I worked in Silicon Valley at Symantec. These days I'm a CTO and mentor people myself. Great video.
Michael, thank you for the fantastic videos! I'm trying to pick a language for my project - I'm deploying ML models on a server and running them on my backend/deploying docker containers. Which language do you think would fit better under the following constraints: 1) A lot of networking programming: preferably with pre-existing tools to deploy my models/apps 2) Good Python integration: most of our code still runs on PyTorch, and I'm not sure we're going to do inference in other language in the near time Thanks!
No idea how ml fits into your design, but if I remove ml from the equation, golang is great for networking, there's a reason why kubernetes et. Al. are built using go. Rust is great for python Interop. Lots of good crates for python Interop with rust, since both python and rust "speak c".
In project setup: - Running 'go tool' in http mode will not only give you flamegraph but other nice visuals. In Syntax: - Their is no need to convert to rune. - for debugging purposes there is no need to use fmt.Println, you can use built-in function 'println()'.
Rust has built-in benchmark tests that are as descriptive as benchmarks in Go - the only thing you need to add to use built-in benchmarks is #![feature(test)] 🙂 more on benchmarking: nnethercote.github.io/perf-book/benchmarking.html
Project setup in go has nothing to envy to cargo. Go module and Cobra and viper are fantastic for command line. Go modules are fantastic for dependency. You don't need generic to write complex application thank to go interface. However since 1.18 generic is supported. Regarding syntax, go is much simple to learn and faster to write for average developer. Go routine is so easy to implement. The overhead is ridiculously low....
I find Go more practical and easier for web servers, which is all I do. I agree on the huge appreciation of Rust by the community, but I believe one should use right tool for the job. For web, Rust is an overkill.
In rust you can get chars of strings using the standard indexing syntax, which is way more performance. When using your weird chars method, it creates an iterator, counting up until the expected element is reached, which is horrible for performance. EDIT: I think the indexing thing is even more naive
@FichDich InDemArsch I mean rust indexing is byte indexing as it is fast etc. but in the example he showed that wouldn’t be a problem as if I recall it correctly, he only used aasci characters.
you can create static binaries in Rust using musl and apline images docker images, so I don't really think that Go is that much of an advantage here. Although if an application is string heavy Go may be a little easier.
@@kylek.3689 Not knowing something isn't an issue, as any half decent engineer will know how to search for solutions. Setting up the tooling is insanely easy via the cross project, one simply invokes: $ cargo install cross Done.
Amazing, someone that actually did the research. Still, I'd argue that Go and Rust both have their niches and will probably stay strong in them, assigning an overall winner is maybe fitting for you but for most others an oversimplification. The one thing where I'd have wished you'd have taken a broader approach is stuff like embedded programming and compatibility with other languages. I assume at some point rust is gonna take over most of embedded programming from C and C++, even if just for its memory security. Also Rust has some Foreign function interface capabilities for C and C++, I'd love to know how well Go handles that. One thing where I personally don't agree with the praise of Rust is its type system. Yeah its a vast type system and you can do a lot of stuff with it, but I think they did a few things that make it quite unpredictable (or "arcane") that could've been done better. Maybe I'm just spoiled by Typescript. But in a lot of cases I understand why Rusts type system is limited, but in others I wonder if it shouldn't have been limited some more, while providing some freedoms elsewhere. One specific thing I'm not a fan of is how in Rust type information can "flow up" the lines of code, I. E. For a std::Vector, you can declare it without elements or a type which gets inferred from the assignments. This makes the type system sometimes quite flaky. If Rust wouldnt do that it would make a feature easier that I personally would really love, that is inferred return type. Especially with a Result with many error options as return type, the return type can be quite annoying to handle and break your compilation all the time.
Rust can make a static binary with no dynamic linking, too. Basially, if you can do it with C, you can do it with Rust. In Go, you're stuck with the runtime and GC (Tokio/RAII is far superior).
If(want to get shit done quickly == true) { use Go} else if(Want to do closer to low level shit that is very performant such as a game engine == true) { use Rust}
Also, as far as compilation speed, Rust is slower, the slowest among "compiled" languages, but Go is not faster than D (unless you write really heavy template meta programming stuff).
Since Go doesn't support generics and all variables must have type declaration, the compilation speed is HIGH. But Go will add generics and inferred types, so compilation speeds will go down
> But Go will add generics and inferred types, so compilation speeds will go down Probably. But generics are such an important part of software development that it's a very good trade off.
Great comparison. Like some others I thought this was a bit click bait, but scoring made it pretty empirical. Not used Go in years but love Rust because of its features and ecosystem.
Thanks for great video! I never be able to compile golang into a static library. Upon checking with ldd or readelf command, it still depends on standard library dynamically. Compile with -static flag won't help me either. Any suggestion on this? (I use go1.17.3)
You forgot the amazing message compiler error in Rust, it's so amazing that I feel pity for the C++ one. Also, the syntax in go is so easy that you can read go code just fine, even if you have never seen go code before (except concurrency)
I'm very new to Go, & programming in general, but it seems like at 24:15 you are comparing the concurrency(goroutines) of Go to the same in Rust(tokio) but you did not use the "go" keyword in front of the func definition while you did use the "async" keyword in front of the same definition in Rust. To my knowledge, you aren't using concurrency in go without calling "go" on the function definition, which would explain the lackluster experience you had... Go also seems to emphasize that concurrency & parallelism aren't the same, I'm no CompSci student so I'm not versed on the difference. Please let me know if I'm misunderstanding... Thanks for the very informative content!
I'm sorry, maybe the "go" keyword is used at the function call, not definition... I just wrote my 1st lines of go code the other day & it's a 120 line web scraper so I haven't used any concurrency yet, again very new... Thanks in advance for any clarity!
The concurrency happens on line 79 in go, and on line 94 in rust. On line 89 and the keyword async in the rust code gets the Tokio crate and the compiler to set up boilerplate code to be able to run async concurrency respectively.
26:30 glibc vs musl issues sometimes apply to Go too. For example we weren't able to find full replacement for libvips in pure Go. Vips has Go binding, but it requires glibc. So I needed to rewrite some parts of libvips in pure Go to make our service work on Alpine.
It is pretty performant for what it does, but calling nth() on the iterator over and over again isn't. If you need repeated access to a string via index, and you can guarantee that your data only contains ascii, then use as_bytes() to return a slice to the internal [u8] buffer. Then you can use [ ] to index into it. If you can't guarantee ascii, then you'll have three choices. 1, write the algorithm while only iterating the Chars iterator once. 2, collect the values into a Vec then use [ ] on that. 3, do performance benchmarks to see if multiple iterations through different Chars iterator will be faster than allocating a Vec once.
Nice analysis. At 24:08 you compare the go vs rust concurrent dowork function. I wish you would've use the more compact func signature using the keyword int only once instead of repeating it 4 times. (a, b, c, d int)
Oops someone else already said this Could your validate extra function just use the chars iterator directly? You would still take the first character as you did before, but then you'd do for test in password.chars().skip(1).take(5) { // rest is identical } there could be something wrong with this that I'm missing though.
Rust is really annoying to setup crates in, because you _HAVE_ to specify a version string for every crate, and the format is really weird. Sometimes you just want to use the most recent version.
What do you think? Is RUST going to be next big thing in terms of opportunities? I know 4 languages, should I pick Rust as next one? BTW I am currently working on Golang.
I'm a little confused about the single binary bullet point in the first comparison... Doesn't rust do this already, albeit with large binary sizes because everything gets popped into the single binary (static linking) by default and you have to turn on dynamic linking as an option?
By default rust dynamically links to glibc. You can enable a static libc like musl if you desire. The problem with musl is that a large portion of the rust ecosystem provides wrappers around already created c libraries. These c libraries dynamically link against glibc; and a binary that links to musl and glibc at the same time will crash. It's somewhat of a young ecosystem problem that go does not have because go is much more mature.
I don't know what you mean by "citation needed", as the link you posted points out " For example, on Linux we can compile not for the current system, but instead for the x86_64-unknown-linux-musl target, to not depend on default system libraries" Here is an empirical test. cargo init qwer && cd qwer && cargo build && ldd target/debug/qwer This will list out the dynamic libraries a default rust binary depends upon.
I'm a Go developer, toying with Rust privately. It's a meaningless question. They have each their strength. ... and I wouldn't know why "heavy use of strings" would make Go better. Go is easy, but Rust handles strings just fine too.
Kind of, with Rust it just gives warnings and you don’t have to do the assign to nothing. But with Go you have to explicitly tell it that you know you’re not using the variable and it’s not a mistake. It’s great for prototyping and honestly I think it’s a kind of activity that people learn to love because when you refactor your code later you’re not stuck with random useless variables.
If you can accomplish the same thing with both (whether more / less lines of code or using native or dependencies) but one wins out in performance AND security then that is the clear winner.
There is one thing I consider important. Libraries and their ease of use. I would rate C++ as evil here. If you want simple programs that uses CSV, XML, JSON, YAML, INI files, does something with images you can write it in Python in hour and 25 lines of code, whereas in C++ you would be barely halfway of writing cmake files. I actually liked Java before I discovered Python, it had many useful features built in. Unlike C++ that can't uppercase UTF-8 string in 2021. And where only standard library is unreadable templated mess that doesn't solve many real world problems.
I think business world will still see python dominate, you might get a few eccentrics trying to build a team for rust and go for line of business apps but I think your right.
Man, you are way too generous. I would give Go 0/10 in syntax for not having generics. How do you write a growable list of your custom struct? Do you need to re-implement the growing algorithm for every type that you want to store in a growable list? That sounds really stupid.
A performance difference between `fn` and `impl Fn` sounds suspicious to me. Could you link to any code sample that's able to repro this? Note that `impl Fn` is actually *static* dispatch, not dynamic. My guess is that if we put together benchmarks that actually did dynamic dispatch in both cases, they would perform the same.
I thought you couldn't dynamically dispatch without using `dyn` in Rust 2018. And fn() is not even a trait object, it's a simple primitive function pointer, there shouldn't be any performance penalty.
@@VivekYadav-ds8oz Fn is the trait. You could do a Box ...> which does dynamic dispatch. Using lowercase fn, means you can only pass functions, not closures. The impl Fn (or really any impl Trait) is similar to generic. You can do fn foo(callback: T) { callback() } instead of fn foo(callback: impl Fn()) { callback() }.
No, Go does not depend on libc. Go has its own standard library that includes implementations of commonly used functions such as file I/O, networking, and memory management, so it does not rely on libc or any other external library.
That’s it I’m writing a language combined with the good from both Go and Rust. I shall call it Gusto.
I can unironically see a language named that way
Just goRust :)
Ok, but if it has a GC, I'm not touching it! I think that Zig may have beat you to that design, by the way.
or GUST ? 🤣
Just call it C+++
23:41 "Rust Runtime crashed when I introduced ridiculous levels of parallelism"
This is because std::thread is not the same as tokio::task or Go's go.
std::thread actually creates an operating system thread through the OS's native interfaces, and the operating system can impose a limit to the number of threads that your program can spin up. It has that overhead of the OS needing to initialize structures and put it in the scheduler.
tokio::task and go, meanwhile, are greenthreads. These are fake threads that are scheduled by the tokio or go runtime, and executed in a pool of working native OS threads.
OS threads are better for getting the most that you can out of your computational and memory resources. Greenthreads are better for I/O heavy tasks where, most of the time, the threads are sleeping while they wait on networking or filesystem operations.
14:40 this is not how to iterate over a string (or the characters of a string) in rust, and it's not a naive solution either IMO
.chars().nth() cannot be compared to indexing as it does significantly more work.
strings in rust are utf8 encoded. they are a wrapper over an array of u8s, where a character may be multiple bytes wide (i.e. 🦀 becomes [240, 159, 166, 128]). because of that, you can't index into the string and expect to get a char out of it. at least not in O(1) time. if you do want to index into the raw bytes and get a single byte (not a character) at a specific index (which is what the code does), one can use str.as_bytes()[i].
this is what Go does when you index into a string. "🦀"[0] returns "ð", not the real emoji. "🦀".chars().next() returns the emoji.
when you do .chars(), an iterator over the characters is created, and every time you call `.next()` on the character iterator (which `nth()` does), it walks the array of u8s as many bytes as it has to to decode a full character. `nth()` continuously calls `next()` on the iterator as many times as it has to yield the character at a given position.
this means that creating an iterator and calling nth *on every iteration* means walking the string from the start of the string, up to the character index every time. you effectively have to decode the utf8 string on every iteration up to character index `i`.
the solution is to create one iterator outside of the loop and just iterating over it, so you don't start decoding the utf8 string at the very beginning of the string on every iteration, but where you left off in the previous iteration.
if you were to compare this to equivalent Go code, you would have to add a for loop that does nothing but iterate from 0 to i before getting the character inside of the loop.
also, iterators often eliminate boundary checks, so thats another reason to prefer iterators over manual for loops
again 21:05 and 27:48 mentions how strings in Rust are inferior, which isn't true. the code that was shown was not a naive solution that someone knowing utf8 would implement. other languages either encode strings differently, such as javascript where strings are UTF16, in which case string indexing just works, or it's a hidden implementation detail and indexing is O(n), *or* indexing just returns the raw byte at a given index (like go).
27:18 rust is data-race free, but not race condition free. race conditions aren't memory errors, and they are safe.
Give it to him! I was wondering myself what he is talking about. Strings are pretty well handled in Rust. Well IF you use the String struct and not raw u8 arrays.
I wish I could upvote this comment more than once: the reason the "naive" rust version is slow compared to the Go code is because they are doing completely different things.
Looking at his code, you can see that he really doesn't know either language very well. And simply mapping his C and C++ knowledge to both languages. Not even sure why he's casting the byte to a rune in Go. In Rust, beyond using as_bytes(), it would have been better to use windows() to do the sliding window rather than coding it manually.
@@pedantic79 ya cuz it was day 4
In all fairness, it is "naive" what he's doing. The problem with these languages, and also C++, is that they layer a bunch of opaque abstractions, and so hide some pitfalls. In C, the naive solutions that an undergrad may write are usually just as fast. In fact, the biggest issues with these languages can be summed up in one word: complexity.
Nice comparison, but I still think that I would strongly consider Go. If I were starting a company today (I did in 2000, but that was before Go or Rust; we used Java), I would also have to consider how difficult it would be to hire good developers. If my project was written in Rust, I would need to find developers who were already competent in the language. If I chose Go, I could probably hire people who were already expert programmers in any language, even if they did not know Go. I could give them a copy of a good Go book (The Go Programming Language, by Donovan and Kernighan, maybe, or one of the newer excellent books) and know that the new developer could learn the language in few days.
Exactly. Rust takes months to get used to and code well in. Go takes days.
1. To start project in go just say "go mod init ".
2. There's good way for project structure not wrong way except files in one folder is package have to same pkg name and all your packages in module.
3. Testing is more then unit testing in go you can make benchmarks, code m coverage, race detection, profiling and much more with only command line flags.
4. Unused variable and packages also non implemented functions increases compilation time.
5. Note: Concurrency is different the parallelism and go implemented both the examples you've used in this presentation is more like parallel version. To use goroutine in jus type go in front of the function if it's returning values or talking to other goroutine use channels not wait groups.
It feels like the point of Rust's compiler was missed here... yeah, it's phenomenally slow, because a point that's regularly emphasised with it, is that it's running slews of compile-time checks for things like memory errors you'd encounter in other languages, to the extent you'd might be able to call it something like "Compiler Oriented Programming"...
Just buy a super fast CPU. On my AMD 5950x it compiles pretty fast.
Because rust trade off is the compiler speed. And that is what makes rust good because it does all the work in the compiler time and ensures max runtime speed and safety
You might've heard that there's nothing without trade-offs in Computer Science. And when a language says there's no compromise, there's probably a hidden asterisk. For Rust, this is it. Yes, you choose all 3 (reliability, correctness, performance) at runtime, but the secret trade-off is development time.
@@VivekYadav-ds8oz I wouldn’t say “dev time” is is necessarily sacrificed with rust, mostly because I don’t think compiler performance is super critical for dev productivity. I would know, because I work a lot in Typescript with Webpack, and 90% of my dev tools are written in JS/TS.
@@dupersuper1000 I have to disagree, compiling time is quite important, I mean half the reason go was created was to have better compiling times, and that is a completely valid reason for choosing a language over another, if you don't mind spending a bunch of your time waiting for your program to compile then you can reach for rust to gain other things with the cost of additional compiling time
Initially I thought "Nice clickbait", but it turned out not to be clickbait, good stuff man!
You discovered my evil scheme. Trick them with a clickbait title but then do my best to ensure that it's not a clickbait title.
@@masmullin As Veritasium quoted someone, "Legitbait".
@@masmullinif only the rest of the internet thought like this…
While Rusts Strings can be a bit harder to work with, they are at least 100% correct. It forces you to handle the Utf-8 correctly, which can be a bit annoying for simple scripts, but is better for bigger projects.
From both the Rust and Go code, it's clear that Michael hasn't worked with UTF-8. In the Go code, he operates on bytes of the string, which isn't unicode safe. He does cast a byte to rune multiple time, but I'm not 100% sure why. In Go, if you need random access to a unicode string, then you'd need to copy the string into a rune slice. In Rust, you can gain access to the bytes of the String either with as_bytes() which returns a slice to the internal Vec, or bytes() which is an iterator of the u8. If you need code points, then use chars(), you can always collect into a Vec, but you need to make the choice to allocate the Vec.
6.5/5 would watch again. 😁 I like that you actually show some code examples side by side.
Almost 9 years of C straight.. sounds like a prison term! A minimal security prison though...
that has to be the best programming pun ever
damn.
@@raianmr2843 agreed!! 😂🏆
Having someone with such an intense background in development do a deep-dive like this is INVALUABLE. Thank you for taking the time to put this presentation together - I hope you give this talk at a few conferences to help spread the info. It was great to listen to.
lol... he got so many things wrong
@@jaromor8808 I'm not familiar with either enough to know so I'm super curious - what items were wrong?
Just so you know your validate string examples are not an exact comparison.
Rust strings are always valid UTF-8. This is not true for go. Go can have valid utf8 but its not enforced by the default string.
For your GO code what you do is grab the byte out of the string. Depending on what you wanted the byte could be invalid(if its the second byte of a utf8 character). You then turn it into a rune. I'm pretty sure that does "nothing" as its not going to change it to UTF8. But you can then use it as a "char".
In the original rust code you get a UTF8 character out of the string. This requires parsing up to the first 4 bytes. You then do this over and over again. To get the Nth character in utf8 you must parse the previous ones. Basically you were doing more work then you needed to.
You could have done something like.
If pass.is_ascii(){
Let bytes = pass.as_bytes()
Prev = bytes[0] //you know its ascii and can treat it like the go string or more correctly a non null c string.
...
Else{// its utf8, you must grab chars not bytes
Let prev = pass.chars().nth(0) //could even be replaced with the first next() iterator call.
For char in pass.chars().skip(1).take(5){
...
I typed this on my phone so there may be a few errors. Basically the restriction you talk about is less of a rust restriction and more of a utf8 one. A pure GO(c or c++) utf8 string would have the same issue.
Hope that all makes sense.
the original rust version, would not fail with utf-8 strings, but be slower. While the go version would fail. An undetected, but correct slow version can be optimized later, but an undetected wrong version, can cause serious trouble in production. I think that correctness is somehow overshadowed by performance (There are verry little public available correcness metrics, but if your program is fast (or not) someone will boast about it (i mean i do that too)). Rust does not compromize on correctness. And theres where it relay shines in my opinion.
Yes im totally biased to rust.
@@benmuker6300 Yes, but i think the issue was more that he attributed the slowness/weird api to rust. Where the issue was more that rust uses UTF8 by default where go does not.
You would have the same or a similar api for GO Utf8 strings.
He just didn't write a fast path for if the string was ascii. He also did probably the worst implementation for checking strings.
But none of that was Rust specific only Utf8.
I also disagree on his fn vs impl fn argument. As well as his String vs str.
Rust chose correctly for both of those cases. It just may not be clear as to why on first glance.
But rust is a great language for correctness.
(edit: update to actually be the correct function ^^')
This handles utf-8 correctly, and is quite fast
```rust
fn validate_extra(password: &str) -> bool {
let mut rc = false;
if password.len() < 6 {
return false;
}
let mut m = '0';
// Safety: It was earlier checked that the password contains at least 6 characters
let mut chars = password.chars();
let mut prev = chars.next().unwrap();
for test in chars {
if (test as u16) < (prev as u16) {
return false;
}
if test == prev {
if test == m {
rc = false;
} else if m == '0' || rc == false {
rc = true;
m = test;
}
}
prev = test;
}
rc
}
```
@@erikfundercarstensen7344 As long as you use an iterator it should be faster. Calling nth is like multiple calls to strlen. Its just not the right thing to do.
Also str::len returns byte length not number of chars. For that you should iterate through and take 6 characters then check if next is none. If its not the length was wrong. You should also use an enumerated iterator so at the end you know if the take took 6 characters.
They way its done here you could only have 2 characters. One 4 byte and one 2 byte char.
I forget the default behaviour of as. If it just trims your as u16 comparison could be wrong as well. Or even if it just sets u16::max it still could be wrong.
Utf8 just complicates strings so much its hard to get right and performant.
@FichDich InDemArsch Sure, like most people won't document or do error handling. Maybe a better example would be checking strlen or nullptrs. But we know those things are wrong. The issue is just that UTF is a great standard, but has quarks. It's less of a reflection on rust.
I like writing code using TDD and the amazing speed go compiles makes red-green-refactor really nice.
I love Go and Rust as language to develop projects. Both are powerful ver well. Thank for reviewing both.
That’s a good talk! I guess Rust compilation times are slow(er) because it does a lot of checks behind the scene (I’m pretty sure you’ve seen Rust compilation errors telling you about dangling references etc, that gives me a feeling of running a static check analyzer and they’re kinda slow), whereas Go just compiles the program as long as it’s syntax errors free, but it leaves you with all the dangers of null pointers, dangling references and so on.
You're probably right with the comment about Rust, but I think there is more going on with Go than just checking syntax.
C just checks for syntax as well, but it compiles significantly slower than Go. C syntax is also simpler than Go. So there must be more to it's compile speed. The Go compiler speed is really dark sorcery of some sort.
@@masmullin I’ve found some info. It boils down to Go just being smarter about analyzing what and how to compile, here’s more detailed explanation: talks.golang.org/2012/splash.article#TOC_5.
The string thing about rust was because Rust uses unicode strings by default. They can't be indexed, because chars are not fixed size. The "string.chars().nth(i).unwrap()" actually loops through the entire string up until the character index you requested.
This is a significant difference from C, where strings are just arrays of bytes, with ascii encoding assumed.
Rust is weirdly pedantic like that in many cases. For example, sorting by float can be pain, because they lack total order property due to NaNs.
Just to add more context.
Strings(correct utf8) have to be implemented this way in any language.
The float issue is also not rust specific but stated by the iso/IEEE standard. I'll have to double check but i believe there was actually new guidance on this in the latest release.
@@dynfoxx Yes, it's just that rust is one of the few languages that take correctness this seriously.
Usually, languages take the simple route of treating strings as array of bytes, and floats as sortable. Treating utf8 and NaNs as special exceptions where, the default code is expected to make no logical sense or fail.
Really appreciate the comprehensive breakdown of the strengths of each language. I'm currently a comp sci student taking C++ courses and both the syntax as well as ease of use of GO has me jealous.
Look forward to more videos!
Same here. Thinking about picking up Rust for uni projects. Already using Go for concurrent stuff.
For your slow string implemenation, I think that your Go version should have given you the insight that you're traversing a UTF-8 string. I think that your initial version that was slow converted each UTF-8 character in the string to a UTF-32 (rune) array on every iteration of the loop. Since Go would have given you a copy in a new allocation each iteration, I'd bet a Go version of that same procedure would be slower. Also, Go and Rust syntax are both pretty good. Syntax isn't as much of a human concern as it is a machine concern speed wise, but human understanding is manditory. I believe that they both do a better job than C++ and maybe C. Zig almost looks like Rust because it was already very good.
With the new version of Go supporting Generics, you can argue that Go would be the winner in this debate, because you counted 1 point off because it didn't have generics.
Also, you can check for race conditions with the:
"go run -race main.go" command
It will not compile if you have race conditions, just like rust.
So when everything is added up, Go wins by a .5 point margin.
IDK. I have only seen the syntax portion of the video where Michael compares strings but fails to realize that Rust uses utf8 and Go uses ascii. It made me stop watching the video. FYI Rust and Go are meant for different purposes one for RAM memory manipulation and the other for running IO bound services. Rust can work better than Go with some effort but Go can't be a general purpose language like Rust as Rust works on a more fundamental level.
Frankly for me the very fact that they were comfortable holding out on Generics for so long (with that lack afflicting a lot of other design decisions in a significant way) is showing.
Rust has held out on async, but frankly that feature is:
1) far more complex (at least if you target as broad as a spectrum as Rust, including small embedded devices)
2) rust solved it quite well and unique with its support for multiple runtime and its postfix await, while go's long awaited solution for generics is... Generic (which isn't necessarily bad, but it makes the long wait all the more annoying)
3) just plain not as important for the core language and with limited broad applicability. It did affect some parts of the ecosystem, but was highly localized, and in the end these designs were fixable.
Also, your part regarding race conditions is wrong. First, small nitpick, go run -race will compile code with race conditions, it will just test for them while running, and then error out.
And, more importantly, and kindof connected, as I said it will test for race conditions while running, which isn't perfect, as it WILL miss race conditions if they're hidden behind some conditions that are not reached during the run.
You can detect memory races in just the same way with any C/C++ program (and many other compiled programs, also Rust for that regard) using valgrind. It's far less comfortable than Go, it's yet another tool etc, but it's not a standout feature that makes Go special, as it was tacked on by Go without designing the core language to be safe, like Rust did.
Rusts detection of data races is far less flaky as its based on static analysis (in an ergonomic way due to it being part of the type system), and it's impossible to miss a data race, well, without unsafe that is (where you want to minimize potential, and if you have to use unsafe you can still use valgrind etc. Considering how isolated potential issues are your option space will also be smaller due to localized use of unsafe far with Rust, thus its easier to a sure all conditions are traversed.
Personally, I am very happy with go's insisting on using declared variables and imported modules. Letting that slide makes for problems later. In C, it's #include , in Go, it becomes gratuitous dependencies that really don't exist.
Throwing compilation errors is great for trimming down production code, but terrible for debug builds, which a developer is far more likely to be on the receiving end of compared to production builds.
@@kylek.3689if you use vscode, the golang extention proms you to install go tools like, gopls, goimports, dlv, etc... and with those tools vscode marks unused variables like error and other things too. also you have auto import of packages. and with the command go mod tidy go clean all unused packages from go.mod file (it is similar to package.json but way better). also for debugging go is king, i tried to debug in rust and it was an awful experience.
Thanks. Especially from the point of view of a programmer with previous c/c++/java experience.
Hey, I left a few comments that were slightly critical about the languages, but please don't take them as criticism of your video. It's a great video, and I enjoy the subjects of Go, Rust, and programming languages in general. Please make more videos on this subject. It's great fuel to forge ideas about what's best to use, and why.
One aspect is completely missing where Go blows Rust out of the water big times: Ecosystem maturity. IMHO this is THE most important aspect of a language because a language with a young ecosystem will not get you anywhere. Now, Rust's ecosystem is not entirely bad but it's young and especially everything network-related is a mess thanks to async vs non-async vs tokio vs async-std, you name it. So in regards to networking, the ecosystem is actually split which is much worse than simply be unmatured. That's really a problem in the Rust world and currently there is no cure.
True. Go being a "server side" language definitely adds to that. I tried writing a server side application and it's just not there yet because of libraries not being async compatible. I feel like Rust will eventually catch up though and when it does my guess is it's going to be UGE
Strings are not arcane in rust, they are pretty well handled, it's just that what you did is completely wrong. You need to understand that in utf8, you never know wether a character will be 1, 2, 3 or 4 bytes, so getting the nth character means that you will have to go through all the preceding characters as you can't just say that you take the nth element of the bytes array. For example, é takes two bytes, so if you want chars of "héllo", 1st char is h and takes just 1 byte, but 2nd char é takes two, so third char is not the 3rd element of the array but the 4th, that's why the complexity of .chars.nth(i) is O(i), so instead, you should just have iterated through .chars() with a for loop. Then you would have gone through the string without any issue in speed.
So you didn't need runes or anything, you just needed plain
for c in my_str.chars() {
// do something with `c`
}
which is waaaay simpler than what you did
I had a good blogpost about character encoding in the past but lost it. I just googled the thing and that post seems legit, so it might be a good read on the concept
medium.com/jspoint/introduction-to-character-encoding-3b9735f265a6#:~:text=A%20character%20encoding%20is%20a,characters%20based%20on%20their%20values.
There is also that for string handling in rust stackoverflow.com/questions/22118221/how-do-you-iterate-over-a-string-by-character
I think the community and package manager really make a language. Most devs (outside creative hubs in US) need a language that’s supported on AWS and Azure. While I keep an eye on both rust and go, neither will help me or my clients go more than what I use python and c# for. 90% of jobs in my area need these skills
Great video, but I would suggest you look into ways around the "must be used" variable issue for go. It is a pain in the butt at first but there are ways around it. I would also argue channels in go are a little easier to work with compared to rust. For rust, cargo check is your friend, it lets you ensure code will compile much faster than cargo build, that way you arent waiting for code to compile since rust is slow just to have it fail 80% of the way.
From my experience building a moderately complex to a highly complex system, Go's development time is very less than compared to Rust.
Rust sure is fast but it also takes time to develop in it, whereas with Go development time is minimal compared to Rust.
It all boils down to simple things
Fast development, fast program, mature ecosystem => Go
Considerably more time for developing, extremely fast program, memory checks, risk takers ( coz its in early stage and stability is important for long term projects) => Rust
I think rust definitely takes more time to get up and running in when it comes to development time, but once you understand to work with the burrow checker instead of against it the development time becomes comparable with other languages.
You will never reach productivity on the levels of python but it becomes comfortable.
Rust itself is fairly stable in its syntax, now with async await being stabilized, and they make a great effort to be stable overall.
I will however give you that the ecosystem is still very much in development and many libraries should be considered unstable.
@FichDich InDemArsch I haven't had much issue with lifetimes since I figured them out, and it really helps to use the rust provided structures when dealing with data ( cell, refcell, ect. ) .
I can't really say much when it comes to refactoring since in the 2 big projects I've done in rust I haven't had to do yet.
This comment shows he hasn't done any development in Rust. I've actually done a bit of Go at a job and I do Rust in hobby protects. Rust eco system follow the npm model. Claiming Go outdoes that is factually wrong, especially the arguments laid out in your comment. With the eco system literally built into the toolchain, Rust outperforms Go in this regard by an order of magnitude. The question is if the npm model is to prefer or not, that is an entirely different question. I like it, especially for things I do in my spare time, since you literally can get something up and running in a few minutes by pulling in some dependencies.
@@simonfarre4907 Cargo is king. My wet dream is Haskell with Cargo.
For the problem at 16:10 you probably should have iterated over the string with the password.chars() ie
for test in password.chars { /* do stuff */ }
yes exactky, I think he should have taken a `AsRef` instead of a `&[u8]`
@@aniketfuryrocks I'm literally on day 1 with Rust, and I've already learned that iterators are much more performant than indexing...
Silly to have an 'overall' winner. Go has generics now, so is this invalid now?
That's a nice comparison. One thing that's different between is the ease of learning go. But in like rust alot due to memory safety performance.
I have a theory why rust refused to launch your million threads: when using standard rust threads you’re relying on actual Os-threads and when using Tokio I think you’re using green threads
@FichDich InDemArsch yeah but the guy in the video acted like he didn’t know and I didn’t even say that it wasn’t obvious
@@user-zd9wd it's also not necessarily green threads(Rust async is a generator and does not grow). Rust async(tokio, mio) just use a work stealing model. You end up with around the same number of threads as your machine has cores. Epol is then used to dispatch events back to async tasks.
After using other languages for a while, and now experimenting with Rust and Go, what are your thoughts on C now? Would you still choose it for any use cases besides the obvious compiler availability ones?
That's a very good question. Short answer is that there are a few areas where I would use C. Most in "non shipping" scenarios (ie testing code).
The place I prefer C in a shipping product is bpf code.
I will still use C/C++ for embedded devices and graphical tools.
It was really nice comparison, very deep analysis of both languages, but it would have been great if you have shown some real time statistics, like when you say rust is at least twice faster. Thanks
Thank you sir. I watched all the way through. I appreciated you showing side by side code. I had already chosen Rust as my next systems level and performance/security oriented language, but I wanted to understand where and when Go might be implicated. Very clear. Even after just two days using Rust and an hour or two on Go your information was clear and relevant. One hopefully constructive comment: on the comparison slides where the bullet points for the each language's score for the given slide topic, the bullet items are often just the inverse of the other language. That's fine and appropriate for the slides themselves, but when presenting you could be more succinct and possibly add additional color or show more code by condensing the spoken delivery to just saying the statements once, as in something like: "Go has a clear and intuitive way of providing an abstraction of treating a string as an array of characters, whereas Rust uses an arcane syntax." You could now use the extra time to expand on that statement rather than repeating it more or less for each language. This is something I was taught 30 years ago by a mentor when I worked in Silicon Valley at Symantec. These days I'm a CTO and mentor people myself. Great video.
Go is more developer friendly and easy to learn n understand.⚡
Yeah you clearly did not go through the rust documentation
@@Someone-q6f5x I am a Rust as well as a Golang developer.
@@Someone-q6f5x He’s right. I’m working on Rust, Elixir and Golang.
@@victorray9369 hey I'm learning Elixir right now!
Go is more developer friendly? In what universe?
love you man, helped a lot even though some codes were not the conventional rust way, it really showed your point regardless. Thanks again
Michael, thank you for the fantastic videos!
I'm trying to pick a language for my project - I'm deploying ML models on a server and running them on my backend/deploying docker containers. Which language do you think would fit better under the following constraints:
1) A lot of networking programming: preferably with pre-existing tools to deploy my models/apps
2) Good Python integration: most of our code still runs on PyTorch, and I'm not sure we're going to do inference in other language in the near time
Thanks!
No idea how ml fits into your design, but if I remove ml from the equation, golang is great for networking, there's a reason why kubernetes et. Al. are built using go. Rust is great for python Interop. Lots of good crates for python Interop with rust, since both python and rust "speak c".
In project setup:
- Running 'go tool' in http mode will not only give you flamegraph but other nice visuals.
In Syntax:
- Their is no need to convert to rune.
- for debugging purposes there is no need to use fmt.Println, you can use built-in function 'println()'.
Go's module system initialization is just "go mod init", I don't think its that weird, and also has pprof for flamegraphs, but overall, great video!
Rust has built-in benchmark tests that are as descriptive as benchmarks in Go - the only thing you need to add to use built-in benchmarks is #![feature(test)] 🙂
more on benchmarking: nnethercote.github.io/perf-book/benchmarking.html
Project setup in go has nothing to envy to cargo. Go module and Cobra and viper are fantastic for command line. Go modules are fantastic for dependency. You don't need generic to write complex application thank to go interface. However since 1.18 generic is supported. Regarding syntax, go is much simple to learn and faster to write for average developer. Go routine is so easy to implement. The overhead is ridiculously low....
I find Go more practical and easier for web servers, which is all I do. I agree on the huge appreciation of Rust by the community, but I believe one should use right tool for the job. For web, Rust is an overkill.
In rust you can get chars of strings using the standard indexing syntax, which is way more performance. When using your weird chars method, it creates an iterator, counting up until the expected element is reached, which is horrible for performance.
EDIT: I think the indexing thing is even more naive
So then is rust gonna becoming next big thing?
@@Mersal-uj5nh I don’t know but it has definitely a lot of advantages
@FichDich InDemArsch I mean rust indexing is byte indexing as it is fast etc. but in the example he showed that wouldn’t be a problem as if I recall it correctly, he only used aasci characters.
Generics are now part of Go as of version 1.18.
you can create static binaries in Rust using musl and apline images docker images, so I don't really think that Go is that much of an advantage here. Although if an application is string heavy Go may be a little easier.
That 1) Requires knowing what musl even is and 2) Setting up your dev tools to use it
@@kylek.3689 Not knowing something isn't an issue, as any half decent engineer will know how to search for solutions. Setting up the tooling is insanely easy via the cross project, one simply invokes:
$ cargo install cross
Done.
Amazing, someone that actually did the research.
Still, I'd argue that Go and Rust both have their niches and will probably stay strong in them, assigning an overall winner is maybe fitting for you but for most others an oversimplification.
The one thing where I'd have wished you'd have taken a broader approach is stuff like embedded programming and compatibility with other languages.
I assume at some point rust is gonna take over most of embedded programming from C and C++, even if just for its memory security.
Also Rust has some Foreign function interface capabilities for C and C++, I'd love to know how well Go handles that.
One thing where I personally don't agree with the praise of Rust is its type system.
Yeah its a vast type system and you can do a lot of stuff with it, but I think they did a few things that make it quite unpredictable (or "arcane") that could've been done better.
Maybe I'm just spoiled by Typescript. But in a lot of cases I understand why Rusts type system is limited, but in others I wonder if it shouldn't have been limited some more, while providing some freedoms elsewhere.
One specific thing I'm not a fan of is how in Rust type information can "flow up" the lines of code, I. E. For a std::Vector, you can declare it without elements or a type which gets inferred from the assignments. This makes the type system sometimes quite flaky.
If Rust wouldnt do that it would make a feature easier that I personally would really love, that is inferred return type. Especially with a Result with many error options as return type, the return type can be quite annoying to handle and break your compilation all the time.
Agreed on return types being more important to infer than "flow up/back" of types. (also from another Typescript dev)
Robust comparison!!! Thank you
Rust can make a static binary with no dynamic linking, too. Basially, if you can do it with C, you can do it with Rust. In Go, you're stuck with the runtime and GC (Tokio/RAII is far superior).
You said that go is better when it comes to strings but rust is 2x+ faster when converting chars to u8’s. Could you expand on that?
3:20 - “abouut”
CANADIAN SPOTTED
If(want to get shit done quickly == true) { use Go} else if(Want to do closer to low level shit that is very performant such as a game engine == true) { use Rust}
One thing I like about both languages is cross-compilation, which is necessary while developing an embedded system. Way better than C/C++!
Also, as far as compilation speed, Rust is slower, the slowest among "compiled" languages, but Go is not faster than D (unless you write really heavy template meta programming stuff).
Since Go doesn't support generics and all variables must have type declaration, the compilation speed is HIGH.
But Go will add generics and inferred types, so compilation speeds will go down
> But Go will add generics and inferred types, so compilation speeds will go down
Probably. But generics are such an important part of software development that it's a very good trade off.
Great comparison. Like some others I thought this was a bit click bait, but scoring made it pretty empirical. Not used Go in years but love Rust because of its features and ecosystem.
I would use go and rust in different circumstances
Thanks for great video! I never be able to compile golang into a static library. Upon checking with ldd or readelf command, it still depends on standard library dynamically. Compile with -static flag won't help me either. Any suggestion on this? (I use go1.17.3)
I'm simple Python user and Rust scares me... I prefer Go ;-)
You forgot the amazing message compiler error in Rust, it's so amazing that I feel pity for the C++ one.
Also, the syntax in go is so easy that you can read go code just fine, even if you have never seen go code before (except concurrency)
Go has discards, so dont need unused variables.
This was very helpful. Thank you!
Very straightforward, thank you
Glad it was helpful!
Nice comparison💯❤🔥
Another excellent video! Great concise analysis. Please keep these videos coming. Would also appreciate videos on rust and go projects.
Thank you for the video :)
My pleasure!
absolutely. you can see that it's rust from a mile away. and I say that as a dude about to do a job full time in go :D
I'm very new to Go, & programming in general, but it seems like at 24:15 you are comparing the concurrency(goroutines) of Go to the same in Rust(tokio) but you did not use the "go" keyword in front of the func definition while you did use the "async" keyword in front of the same definition in Rust. To my knowledge, you aren't using concurrency in go without calling "go" on the function definition, which would explain the lackluster experience you had... Go also seems to emphasize that concurrency & parallelism aren't the same, I'm no CompSci student so I'm not versed on the difference. Please let me know if I'm misunderstanding... Thanks for the very informative content!
I'm sorry, maybe the "go" keyword is used at the function call, not definition... I just wrote my 1st lines of go code the other day & it's a 120 line web scraper so I haven't used any concurrency yet, again very new... Thanks in advance for any clarity!
The concurrency happens on line 79 in go, and on line 94 in rust.
On line 89 and the keyword async in the rust code gets the Tokio crate and the compiler to set up boilerplate code to be able to run async concurrency respectively.
@@masmullin I know my reply is mad late, but thanks for clarifying!
26:30 glibc vs musl issues sometimes apply to Go too. For example we weren't able to find full replacement for libvips in pure Go. Vips has Go binding, but it requires glibc. So I needed to rewrite some parts of libvips in pure Go to make our service work on Alpine.
I would use the "chars()" function of a string to return an iterator, which I believe should be very performant (and is also pretty idiomatic).
At a quick glance, the solution from Mike Barber you reference in the repo is exactly what I'm talking about here.
It is pretty performant for what it does, but calling nth() on the iterator over and over again isn't. If you need repeated access to a string via index, and you can guarantee that your data only contains ascii, then use as_bytes() to return a slice to the internal [u8] buffer. Then you can use [ ] to index into it. If you can't guarantee ascii, then you'll have three choices. 1, write the algorithm while only iterating the Chars iterator once. 2, collect the values into a Vec then use [ ] on that. 3, do performance benchmarks to see if multiple iterations through different Chars iterator will be faster than allocating a Vec once.
But there is more jobs for Golang ...
Nice analysis. At 24:08 you compare the go vs rust concurrent dowork function. I wish you would've use the more compact func signature using the keyword int only once instead of repeating it 4 times. (a, b, c, d int)
Oops someone else already said this
Could your validate extra function just use the chars iterator directly? You would still take the first character as you did before, but then you'd do
for test in password.chars().skip(1).take(5) {
// rest is identical
}
there could be something wrong with this that I'm missing though.
Hey Michael, if I was to use rust or go as the language on my server, which language would require me to write more lines of code?
Rust
Rust is really annoying to setup crates in, because you _HAVE_ to specify a version string for every crate, and the format is really weird. Sometimes you just want to use the most recent version.
What do you think? Is RUST going to be next big thing in terms of opportunities? I know 4 languages, should I pick Rust as next one? BTW I am currently working on Golang.
Nice video
Thanks
You can mute variables or comment them to compile correctly
I'm a little confused about the single binary bullet point in the first comparison... Doesn't rust do this already, albeit with large binary sizes because everything gets popped into the single binary (static linking) by default and you have to turn on dynamic linking as an option?
By default rust dynamically links to glibc. You can enable a static libc like musl if you desire.
The problem with musl is that a large portion of the rust ecosystem provides wrappers around already created c libraries. These c libraries dynamically link against glibc; and a binary that links to musl and glibc at the same time will crash.
It's somewhat of a young ecosystem problem that go does not have because go is much more mature.
@@masmullin citation needed. Desperately.
rust-cli.github.io/book/tutorial/packaging.html
I don't know what you mean by "citation needed", as the link you posted points out " For example, on Linux we can compile not for the current system, but instead for the x86_64-unknown-linux-musl target, to not depend on default system libraries"
Here is an empirical test.
cargo init qwer && cd qwer && cargo build && ldd target/debug/qwer
This will list out the dynamic libraries a default rust binary depends upon.
Great Job! Would love a Scala vs Julia
The editor's font is beautiful, which font do you use?
I use Lilex which I have slightly modified to be able to be used with powerline.
Thorough and impressive.
I'm a Go developer, toying with Rust privately.
It's a meaningless question. They have each their strength.
... and I wouldn't know why "heavy use of strings" would make Go better. Go is easy, but Rust handles strings just fine too.
You don't have to use the glibc crates though. You can write them yourselves.
Great video, excellent information
At 18:45 you mentioned getting frustrated having to use variables, just use ‘_ = varName’ and that will get Go to ignore it.
rust did the same thing
Kind of, with Rust it just gives warnings and you don’t have to do the assign to nothing. But with Go you have to explicitly tell it that you know you’re not using the variable and it’s not a mistake. It’s great for prototyping and honestly I think it’s a kind of activity that people learn to love because when you refactor your code later you’re not stuck with random useless variables.
I got hangup over how go imports a package that you created in your project
Go now has generics.
in go you will be stuck with standard library. frameworks is kinda frowned upon
If you can accomplish the same thing with both (whether more / less lines of code or using native or dependencies) but one wins out in performance AND security then that is the clear winner.
There is one thing I consider important. Libraries and their ease of use. I would rate C++ as evil here. If you want simple programs that uses CSV, XML, JSON, YAML, INI files, does something with images you can write it in Python in hour and 25 lines of code, whereas in C++ you would be barely halfway of writing cmake files. I actually liked Java before I discovered Python, it had many useful features built in. Unlike C++ that can't uppercase UTF-8 string in 2021. And where only standard library is unreadable templated mess that doesn't solve many real world problems.
I think business world will still see python dominate, you might get a few eccentrics trying to build a team for rust and go for line of business apps but I think your right.
Best for what should be the question
Man, you are way too generous. I would give Go 0/10 in syntax for not having generics. How do you write a growable list of your custom struct? Do you need to re-implement the growing algorithm for every type that you want to store in a growable list? That sounds really stupid.
A performance difference between `fn` and `impl Fn` sounds suspicious to me. Could you link to any code sample that's able to repro this? Note that `impl Fn` is actually *static* dispatch, not dynamic. My guess is that if we put together benchmarks that actually did dynamic dispatch in both cases, they would perform the same.
I thought you couldn't dynamically dispatch without using `dyn` in Rust 2018. And fn() is not even a trait object, it's a simple primitive function pointer, there shouldn't be any performance penalty.
@@VivekYadav-ds8oz Fn is the trait. You could do a Box ...> which does dynamic dispatch. Using lowercase fn, means you can only pass functions, not closures. The impl Fn (or really any impl Trait) is similar to generic. You can do fn foo(callback: T) { callback() } instead of fn foo(callback: impl Fn()) { callback() }.
great, cheers
Hmmm.. many points for each language seem to have been missed.
Go has generics now.
This guy actually talks about environments, compilers and ecosystems. This isn't a talk about languages themselves.
its language itself if i write a compiler that means i created a (simple) language
Syntax is like, one-fifth of what makes a language worthwhile.
Go also depend on LibC
No, Go does not depend on libc. Go has its own standard library that includes implementations of commonly used functions such as file I/O, networking, and memory management, so it does not rely on libc or any other external library.
too close each other - less than the statistics error of the evaluation method
learning rust now and i can't say syntax complexity is the same. rust is way more complex imo.