@@sinistergeek yeah, immediately after he released it, he made it paid for. While I don't mind if you charge for something, I think it's real scummy to advertise something as a "free training" then on day 3 make it cost! (and advertise a 'discount').
It's not free. It's 4 bullshit videos of just him talking, and then you have to pay for the actual bootcamp. Good news, there are many free rust training programs that are way better than this. Employers do not care about for profit online "bootcamp certificates". If you pay for this, you have been scammed.
sorry for the 3 week necromance, but, async/await is just syntax sugar for monadic do notation, which itself is just syntax sugar for a series of monadic binds. I don't know what language was the first to introduce the concept of monads (whether by that name or a different one), but Haskell was definitely the first to adopt do notation as imperative sugar syntax, which happened in the early 1990s.
@@ZSpecZA heh. and then mondads came from math. does math have async/await? if so, then good luck to create web server in algebra. of course, it's a syntactic sugar. but it's VERY convenient and creative syntactic sugar. sometimes form matters.
Plus, the do-notation is much more general. It doesn't just work for "Futures" but also "Result", "Option", lists, vectors, .... It's really amazing that there is an underlying pattern at all.
8:53 I may be wrong, but the Box in Vec is unnecessary and will cause extra allocations. You can put the Employee directly into Vec, since Vec already allocates them on the heap, so you won't get a recursive enum.
@@mevishalcoder Well, that's exactly what Vec does. It stores the actual elements on the heap, and just a pointer, size, and capacity on the stack. So, Vec is just 1 pointer and two numbers on the stack, which is fixed-size afaik.
You are right. I think OP was thinking about generic types "inheriting" traits. For example, let say we want to get a vector of any type which implements Send, then you have to do Vec because you cannot put a dyn type in Vector, so Vec would cause an error. But that's not necessary for enums because enums are a compile time defined types.
One design choice by Rust that I consider a feature is the fact that unlike C/C++, variables are const by default and your must explicitly make them mutable. This helps to both guarantee that certain values don't get changed where they shouldn't, and also makes code easier to read because in C many folks forget about const, but in Rust mutable tells the reader of code that the writer of the code specifically wanted this variable to be mutable. I guess you sort of cover it in talking about borrowing but calling out C specifically might catch more people's attention.
6:00 Its not magic. Rust uses the Drop trait for an equivalent to C++ destructors. In your example, the Connection type simply implements this Drop trait to implement the logic to release the connection. It would be identical to having a C++ class called DatabaseConnection with no destructor that stored a Connection class that had a destructor to close the connection.
@@jayp9158 No need to get mad. It's just a correction. Drop is something you learn at the beginner-intermediate stage, and this correction of "magic" to Drop can help beginner developers understand how structs work and how to automate and clean up their code.
I have to say, cargo is also my favorite feature. Just seeing all this green like instead of the crappy mess of makefile is really satisfying. And I feel like is always succeed execpt when some crate relied on C lib
I think the RAII with the sqlite example could be implemented as an std::unique_ptr with a custom deleter(lamda that calls sqlite3_close()). Using a unique_ptr would also prevent unintended copying of the connection and having multiple objects pointing to it.
That’s the thing with c++ - if you do it right (modern c++ paradigms), you don’t have to worry so much about manually managing memory and nitpicking the performance of trivial operations. But people are still vocal about how dangerous and verbose c++ is (well that last bit is still fair point)
in your C++ example about zero cost abstraction, the std::max_element function return an iterator which is the first occurrence of the maximum value found between the two iterators you gave. if you try to read the resulted iterator directly, yes, it will be an undefined behavior. what you have to do instead is to compare the resulted iterator with the end iterator you gave in max_element (which is not inclusive). if those two are equal, it is like a None optional in rust : no max value found because of empty vector for example.
At 6:24, the point you're making about the no-aliasing rule is correct of course, but the example you're giving of invalid code actually compiles without any errors. It would've been an error in an early version of Rust, but since "nonlexical lifetimes" were added, the compiler has been smart enough to see that references you never touch again can be short-lived. To get code that's actually invalid, you need to mention one of the shared references again after the mutable reference was created.
@@Jaood_xDI saw the thumbnail and thought I don’t think JavaScript invented that then looked it up and found what you just said and was just about to comment it when I saw the comment you where replying to and was about to correct them when I saw you correct them 6 min before I had the chance
Apparently Don Syme has stated that his implementation of asynchronous workflow came from a paper wherein a monad for concurrency was designed in Haskell. Not part of an official release of the language, concurrency was a big topic in the 90s. F# was the first language to implement the famous keywords and asynchronous workflow. 5 years later, C# was the first mainstream imperative, OO language to implement it. So, kudos to the Microsoft teams, haha. Also, F# is an incredible language. Very underrated, check it out if you like functional-first programming that still lets you fall back on your bad habits if you want.
2:26 Calling max_element on an empty vector is not undefined behavior, it returns an iterator pointing at the past-the-end element. Dereferencing that past-the-end iterator is UB, so your point stands. You still need to rely on the programmer to account for both cases, meanwhile in Rust the compiler forces you to account for them.
Yeah. That's like what Rust says that manipulating pointers isn't UB, dereferencing one can be. However we both know just because one requires unsafe and the other doesn't, doesn't mean that the UB can't be introduced by the safe counterpart.
@@VivekYadav-ds8oz I think people have trouble understanding what "undefined behavior" really means. Undefined behavior is basically saying that "if you do this thing, we make no guarantees about what would happen." Anything can happen when you read past the end of a vector in C. Nothing could happen, it could seg fault or your HD could get formatted. There is nothing that says "This is what would happen if you read past the end of a vector". The behavior is not defined.
2:29 - No, calling max_element on an empty collection is perfectly defined behavior, and is handled similar to your Rust example. The returned iterator will be numbers.end() and you need to check for that case, similar to how you check for None in your Rust code. The undefined behavior comes in because you unconditionally dereferenced your result without checking. It's the same as if you unconditionally called unwrap() on the result in your Rust example. I really wish Rust people would be more honest about how C++ works and not try to magnify what is and is not undefined behavior. I think Rust is good enough at what it does that you don't need to misrepresent C++ to show Rust's strengths.
Speaking as someone who is new to Rust, and has only done C++ in university... I think the point is that in Rust, you have to explicitly use unwrap() to possibly cause problems, and if you don't... if you just forget to handle that case, the compiler forces you to handle the empty case. In C++, you get the problematic version by default, not the safe one. And it compiles too. Also, in Rust, if you unwrap() when you shouldn't, you're causing a panic, which usually means aborting, whereas in C++, you'd get... idk... srgfault maybe... and get it not immediately on calling the max, but when you later try to use that max.
@@boenrobot My issue is that it wasn't presented that way. I believe what you say about Rust is true, though I'm not sure if you get an error or warning or anything about an unchecked unwrap, but if not you will certainly get a runtime panic rather than undefined behavior. However, the C++ side was completely misrepresented. What was claimed to be undefined was in fact defined if one used the interface properly. Crank up the compiler warnings and I believe some compilers will warn about it, or if not, a linter definitely could. Compile with checked iterators and you'll get a more controlled runtime crash rather than undefined behavior. And if it is still a concern, one can wrap (or reimplement) the standard library to use features like std::expected or any of the other similar types people have written over the years. And that's where I think Rust can make a strong case without misrepresenting what C++ does. Is that as reliable as what Rust provides? Maybe once it is fully set up, but it is more work, possibly less cross platform, dependent on the quality of implementation of the build tools, and in no way a guaranteed available thing, whereas that stuff is built into the language in Rust and available everywhere Rust is. Having that stuff available out of the box is very nice. Is Rust a good enough improvement to make it worth switching? For some domains, absolutely. For an experienced team with their environment set up to catch those sorts of errors, it might be a marginal improvement. There might be other things C++ brings that Rust doesn't have yet. But that can be improved over time. I think Rust advocates do themselves no favors when they seem to only argue against C style C++ or assume one must mess up in C++ but the Rust coder will never write a similar error. Or when they oversell exactly how much safety Rust can actually provide. But for a lot of those things, what Rust does provide is still an improvement even when you deal with C++ in the best light. Some of it will get folded into C++ over time, but some of it can't be.
5:20 It is utterly unclear how one can compare C++ to Rust without ensuring an apples-to-apples comparison. If you are employing a type in Rust that can automatically close connections upon destruction, why would you not utilize an analogous type in C++? Conversely, if you prefer using a low-level API in C++, which necessitates closing connections manually, why would you not employ the same low-level API in Rust?
Simple, because he needs some validation and ego stroking, poor guy can't find a rust job and needs to justify to himself that rust is totally the superior most perfect and elegant language that has ever existed in all of human history and that every other single language on planet Earth does things wrong. Rust just uses magic bro, what are you talking about? Destructors? oh we call that dropping here, and it's done automatically like it's magic! wait, you mean to tell me that every other language with destructors also does it like that? SHUT UP, RUST IS DIFFERENT BECAUSE DROP IS MAGIC!!!!!
Bias is different from intentions, so maybe he didn't realize this while planning the content, arranging it into an order, creating video content, and producing the video. But, if he sees your comment which explains this clearly, maybe he will notice for the future.
Nice drill-down of core features (and how they came to be)! Already "signed up" for the Bootcamp, hope for its release SOONER than later! :) good work @Bogdan!
Thanks for a very informative video. I especially enjoyed how you explained where some of the language features originated and how they are implemented in rust. The originators and community really put a lot of thought into improving the great features of other languages and making a first-class language simple to use.
Most of these examples have been simplified to the point of being misleading. The C++ example for finding the maximum value did no error checking while the Rust example did. The Rust RAII example did not actually close the connection in the DatabaseConnection drop method because I assume Connection already does that automatically. What's the point of that example then?
Async and await wasn't taken from JavaScript. The syntax originated in F# and was quickly adopted in C# as well. From there, it spread to other languages like Python and Javascript, and eventually Rust and C++.
I extended some code last week using traits and at one point the compiler forced me to use a Box dyn thing. It wasn’t obvious why but it might be a similar thing here. But I’m fairly new to Rust.
@@kevinmcfarlane2752 I assume what happened there was that when you use a `dyn Trait`, it needs to be behind a pointer. This can be done with Arc, Rc, Box, or a raw pointer. In this situation, Employee is an Enum IIRC, so it doesn't need the Box.
There is a problem in your RAII examples too : You are telling Rust is better than C++ because ownership is integrated in the language and you don't have to call for a close function to quit the database. But that the same in C++ : Connection may implement Drop to close the connection on its own, like C++ does when the scope goes down. You show a full raw implementation in C++ but not in Rust, that's unfair to compare those code.
Agreed. The Rust example is taking advantage of some other Rust crate (looks like rusqlite) to do the cleanup, while the C++ example is calling the upstream SQLite C API directly. A more direct comparison might have the Rust crate use unsafe code to call the same C functions. That said, the C++ implementation here is also dangerously incomplete. The default copy constructor and copy assignment operator both need to be deleted, or else a simple copy of one of these objects (like passing it to another function by value, or assigning over it) will lead to a double-free and probably a nasty crash. At the same time, an explicit move constructor and move assignment operator probably need to be added. (Maybe it would be better to do all this by just wrapping a std::unique_ptr with a custom deleter?) An unsafe Rust version has its own pitfalls to watch out for, but there's no equivalent of the Rule of Zero. The closest mistake you could make here in Rust would be to explicitly derive the Copy trait, but that doesn't happen by default.
I think this is because in Rust, we only define drop function when creating primitive type and this never happen. And probably we are obligated to implement it in that case
I think you have slightly confused the term zero cost abstraction with the zero-overhead principle. Zero (runtime) cost abstractions mean that when you use abstractions, they should not add any runtime cost versus doing it without the abstraction i.e. unique_ptr has the exact same runtime performance (better in some cases) as a raw pointer, while wrapping it in the abstraction of a smart pointer. Zero cost abstractions are why people like Sean Parent argue for never using raw loops, because we can create abstractions that add zero cost while making the code a lot more manageable and readable and can even allow the compiler to better optimize. It should go without saying that you should never incur a runtime cost for things you aren't using; that isn't because of zero cost abstractions and really overlooks how powerful they are in C++ and Rust.
NPM: 2010... PyPi: 2003... PECL: 2003... PEAR: 1999... CPAN: 1995. Yeah, Node didn't come up with the idea of the library package manager, not even close.
12:29 In javascript, .catch handles the error and whatever you return becomes a resolved value. In this example, getUserData(userId) can now NEVER reject, because we're eating the error with the console.error, I'd consider that a bug. (It either resolves with the json or resolves with the return value of console.error, which is undefined). Instead you would want to do the following .catch(error => { console.error('Error:', error); return Promise.reject(error); } ) // throw error is also acceptable. This highlights why people moved away from manually chaining promises with .then and .catch because there are some funky gotchas you have to watch out for.
However in production code, I'd recommend just not catching the error at all in this function, and having one single catch near the top of the call stack, but that would obviously be a bad example for a video.
@@tckswordscar That should be done in pretty much all languages. Avoid handling errors right there and then, unless you need to add fallback behaviour which doesn't break the flow and UX.
I would rather say that ADT and pattern matching are adopted from OCaml (when traits - from Haskell), but since OCaml is not so known as Haskell, the latter sounds good as well )) Thank you for the video!
Can you elaborate more on 2:50 when you said "There are runtime checks in other languages for this, but Rust is fast"? How does rust avoid runtime checks if it doesn't know in advance the result of max element?
Great video, just wanna point out that did "crates" really come from nodejs npm (2010) or it's idea come from way back with the creation of apache maven (2004) a dependency manager for java applications which also helps in build, test and run applications ?
Fun fact: graydon hoare recently wrote that he didn't want async await in rust instead he wanted a go style concurrency built into the language but others didn't agree to it
Instead of callback hell, we now have promises hell
Год назад+3
Honestly, I think C++ is doing just fine. The examples are just aligned towards Rust. No one is using makefiles directly with C++ these days either. Rather CMake in combination with vcpkg (for package management), is a breeze. vcpkg search package, vcpkg install package etc...
6:06 I understand that the memory of the connection object is freed, but where does rust close the db connection? Doesn't it need to send a signal to the db (or, at least, our local OS) that we don't need the db connection anymore? How do you clean up after yourself in rust?
That example is bad. The C++ example used a connection handle while the Rust one used a higher level connection type, where that underlying type should still implement that exact same close call for it to work. Your type should implement the Drop trait to be exact, which works similarly to a destructor in C++, and close the connection there.
3:00 in Python collections can't change during `max` evaluation(because of GIL), but on the other hand there can be any type of object so Python checks for types(and of course all data in Python is in heap memory) thats why it is not zero cost abstraction, but it is not about `max` function but about the python itself
That looks utterly horrifying in code, probably instanciates 100 templates and makes your compilation time slower and also it is not a feature built into the language but a standard library stuff and thus it can confuse the compiler and make it not optimize correctly. If you are using C++ use unions, anyone who tells you to use std::nonsense instead is probably a psychopath.
I go ahead and add another feature that Rust inspired from C++. Move semantics were introduced in C++11 to address the issue of excessive copying, allowing for the "moving" of heap-allocated objects from one owner to another. However, C++ does not provide safeguards against using the source owner after the move. In contrast, Rust adopted move-by-default semantics, as opposed to C++'s copy-by-default approach, and implemented guardrails to prevent the use of the source owner after a move operation. Thanks for the video ❤
At 6:30 you say that we would get a compile time error when introducing a mutable reference which is not the case. This code snippet actually compiles, the error will only be triggered if we decide to USE the mutable reference while another immutable reference is "active" (or vice-versa)
I have no clue about Rust, yet, cause I'm just watching maybe a third video on it, but shouldn't enum Employee's Worker have manager: Employee instead of string??? 8:33
Obviously an author wanted to show that manager is able to control another manager(s) too. But this implementation looks like any manager doesn't know who is his/her boss 😀
5:02 C++ has defaulted, constructors and destructors too and has had since 2017. Also based on what I heard of rust anytime anyone starts doing anything anymore complicated than basic IO They start using unsafe blocks which , and this is how I have had it explained to me, make it basically writing C in rust syntax...
Great video. I think the async part for JavaScript was not entirely true though. JavaScript is not exactly single threaded. It uses libuv for executing async operations in different threads. By default JavaScript uses 4 threads (defined in libuv) for executing these operations (crypto, disk I/O, dns lookup and ...)
Although what you described is actually related to NodeJS engine rather than JavaScript as language, you’re right that JS does support threading with various implementations of worker threads. I don’t get why people keep saying it’s single threaded, as worker threads have been around for a while now
This is such an amazing video! Loved how you compared each language and took time to look at the pros and cons. I just wanted to add that JavaScript async/await was inspired by C#!
A lot of 'what's borrowed from where' is misleading async\await was first introduced in F# [okay, it's not as popular for your viewers], then picker up by C# [could've used that] and then by Haskell, which you are already mentioning before, and only after that in TS & JS. package managers obviously existed before NPM, Maven being one of the first in regards to programming languages, although I'm not sure if it was the first
I did not know about F#, but definitely learnt async/await in C# in early 2000s. First package repository should be CPAN. Probably Perl did not have something like npm at that time. At least Ruby got RubyGems around 2003. Even Gemfile uses some DSL than JSON/XML/YAML, but it's already a decent modern package manager with lock-file support.
I find it funny that the JS async await is used as inspiration despite JS being single threaded. As a long time frontender that has experienced callback hell, I understand that the web is very event driven and that calling external online resources means that you often need to avoid blocking code that freezes the website for the user. With promises I think I would have written your example by returning the nested promise with the other values you acquired and then destructure the values in the next "then" closure. This would look like: fetch(...).then( val1 => fetch().then(val2 => ({val1, val2})) ).then( vals => fetch(...).then(val3 => ({val3, ...vals})) ).catch(console.error) This way you propagate the error to a single catch, you'll never need to nest deep and you still have access to all the returned values. Although now I just use async await.
How do you even use Rust, "async/await", "object acquires resources". There isn't CPU instruction "acquire resource". I'm just curious, do you memorize it like some tabletop game, like "it's a rustonator of second level, it can do cppnization"? Dijkstra was right.
I'm considering writing audio applications in Rust, but this would require an audio buffer which my code would be continually writing to while the DAC continually reads from that buffer. I'm pursuing real-time audio synthesis, with being able to change synthesis parameters while the output is being read. Would the Unsafe option be enough?
Good job you did give credit to C++ and even provide idiomatic example in first feature of zero cost abstractions. However, in second one there is unfair comparison. C++ database was created on heap with new. However one could create one on stack and have as that simple code as Rust had. For more complex behaviours, you also should provide Drop trait in Rust for some cases. Which is equivalent to destructor in C++.
Eh, more or less unfair. C++ wouldn't automatically call the destructor of a database if it was inside a class that didn't explicitly have a destructor.
@@danielmilyutin9914 in Rust, all members of a struct get dropped when the struct itself gets dropped. In the example shown in the video, you wouldn't need to implement Drop, since we can assume the sqlite object cleans everything up upon dropping (if it didn't, that would be a bad/wrong implementation). You only ever need to implement Drop for very low level stuff. For instance, if you're creating a database handler from scratch, perhaps you keep some numerical file descriptor that you use to talk to the database. Then, you do need to manually close the file using the fd, and you'd do that by specifying drop.
@@henrycgs Thanks for clarifying. But anyway good-designed C++ database library class would provide destructor on its side. And high-level database handle/object will be limited to scope. User must not write/call destructor in this case. Either them uses it in scope of function or in struct/class. This is called RAII. And Rust borrowed this concept in its language design. For me this looks absolutely equivalent to drop. C++ provides special member functions - destructor included - automatically but user can redefine if required. If ones class consists of good-designed objects and no low level resource handling is required, destructor is not needed. Only wrappers of low level resources should have destructors in good C++ code. Upd: And anyway all member destructors will be called in struct/class in C++ . If user mixes low-level and high-level handles in one class/struct (and that is quite bad design on him), high-level handles no need to be mentioned in destructor.
@@henrycgs Sort of incorrect. C++ destructors are always called, for every object, forever. Even objects that don't define a destructor already have one - the default destructor. By nature of RAII the default destructor calls other destructors in order. So, if you have Class Database {} with a destructor and include it inside another class which does not have a destructor, the Database destructor will still be called every time. This is why STL containers like string can be included in your own classes with no work or management. The one exception is deleting the destructor. This is basically never done - because an object without a destructor can never be created on the stack or with new.
First to use that exact system, but not the first one that introduced async conceptually. Moreover, async blew up in JS much more than it did for C# at the time, so it's very possible Rust inspiration came from JS and not C#. Whoever was first doesn't matter in that context.
JS copied it around 2012 when every other language did. Was around long before that. JS is an interpreted scripting language. Not exactly the place to find CS innovation.
15:30 parallelism in JavaScript is not an illusion. There is a thread pool that is managed by node/browser runtime and threads from it are used to run asynchronous IO operations in parallel.
@@Tiritto_ alot of programming concepts are old. What we are talking as “first” here is mainstream. For instance, Java didn’t invent OOP but it sure made it mainstream.
@@metaltyphoon But then if we define "first" as being the one who pushed the concept into the mainstream, then I would argue that JavaScript was indeed the first one.
can macro handle a function that returns a value? Because i want to write std::io::stdin().read_line(&mut String&::new()).unwrap; To prevent the terminal from closing
Sorry if my comment appears multiple times, it looks like it keeps getting removed by youtube and I presume it is because I linked cppreference. You are mistaken in 2:26. From the article about std:max_element in cpprefrerence, passing an empty range to `std::max_element` is not undefined behavior. It returns the second iterator that you passed to it, which points to nothing. The undefined behavior part is dereferencing the iterator, which is conceptually identical to naively calling `unwrap` on the result of the call to `max` in rust. It is the same kind of error. Rust gives you additional value here because it would panic instead of invoking undefined behavior which, in my opinion, is almost always better. Both languages give convenient facilities for checking whether it holds a value or not, however IMO rust's is better.
Yes, the database has a custom destructor. You can implement custom destructors via the Drop trait. It has a "drop" method that runs just before the object is destroyed. Rust runs the "drop" method recursively on the struct itself, all its fields, all fields of its fields, etc. You can never "forget" to destroy something, even if it's deeply nested within composition hierarchy.
@@KohuGaly kudos to this comment! TIL that Drop needs to be ran recursively for all things to actually work out / for the memory to be freed :) I assume when you Derive(Eq), it does those equality checks recursively across all fields too, right?
Rust is like the Frankenstein's monster, a masterpiece that combines the best from the most important and powerful technologies ever created. What a great video, buddy.
Except that C++ code (and explanation of RAII) is not exactly good/idiomatic and thus not comparable to Rust's hand wavy edition... Async/awayt comes from F#/C# and not JS... Etc etc etc This commercial has the same problem as most of the Rust "community" and language in general... Not taking enough time to know other languages well enough to even know how to compare between them...
You mention zero cost abstractions a few times in the video, but I am not fully clear as to how we get zero cost - can you please explain with some concrete examples in one of your future videos or point me to some article that explains this concept
The idea of zero cost abstraction isn't that the code you write will magically work in zero time or anything like that. Rather, it is two things: 1. Compared to doing the same feature manually in C or ASM or other low level language, the abstraction itself should not have any additional cost, but should work just as well. 2. The mere presence or availability of the abstraction shouldn't cause overhead or performance loss in code they doesnt use the abstraction. E.g. C++ has virtual methods, and they are often considered "slow". But compared to a C struct of function pointers to achieve similarly polymorphic code (think struct SDL_RWops or struct sqlite3_file compared to a virtual base class in C++), using virtual methods usually has about the same performance. And if you don't need the indirection, the fact that C++ provides virtual methods doesn't slow down member functions that aren't virtual or add anything to classes that don't use virtual methods at all. The _abstracrion_ has zero cost. In reality, sometimes there is a cost, and sometimes the abstraction enables better optimizations that give a negative cost. Being aware of the tradeoffs of what the compiler is doing and measuring are still needed, as it isn't magical.
@@oracleoftroythanks for the detailed answer. So, if I understand correctly, you get to use a high-level language feature which saves you from having to deal with all the internals of something familiar, but you don't pay any extra overhead at run-time because of the abstraction. Right?
12:52 That's a terrible example. Even before await. Promises were made to solve callback hell by being able to properly chain the .then(). The example can actually be properly rewritten as... fetch('blah') .then(res => res.json()) .then(res => fetch(`blah{res}`)) .then(res=> fetch('on so on'...)); Of course awaits are better and way way readable
Excellent job, Taras! It was the best overview of Rust I've ever seen before! It's short and deep at the same time. It's almost perfect. I'll shurely put it into my favorite list.
I'm curious how you'd replicated Javascript's eager Promises if that was the behaviour you desired? So that you start the async operation independent of where in your code you await the result of it.
I think you can achive it by using tokio's tasks. You could spawn a new task with your Future, get the task's Future and await it whenever you want. That's because tasks will execute when spawned, no need to await them in order for them to do any work.
It's usable. There are hardware abstraction layer (HAL) libraries for most of the commonly used microprocessors. Though the ecosystem is somewhat immature - you may run into lack of support once you go beyond basics.
I know that this is a Rust (course) commercial but still it rubs me the wrong way if C++ is poorly compared to Rust... Es RAII means "if resource is acquired then you get an instance, otherwise you don't get the instance" - that's the "is instantiation" part - so idiomatic c++ implementation would actually be (example both for using exceptions and non-exception code): class db_connection { public: db_connection( std::string_view db_name ) { sqlite3_open( db_name.data(), &db_ ); if( db_ == nullptr ) throw std::runtime_error( "connection failed" ); } ~db_connection() { sqlite3_close( db_ ); } // no need for nullptr check here as it is instantiated by RAII definition... static std::optional< db_connection > make( std::string_view db_name ) { sqlite3* db; sqlite3_open( db_name.data(), &db ); if( db == nullptr ) return std::nullopt; return db_connection( db ); } /* more functions */ private: db_connection( sqlite3* db ) : db_{ db } {} sqlite3* db_; }; int main() { // without exceptions if( auto conn = db_connection::make( "abc" ); conn ) { //do some connection work } // with exceptions try { db_connection conn{ "abc" }; //do some connection work } catch( std::runtime_error const& error ) { std::format("error: {}", error.what() ); } } And it's not nice to compare C++ wrapping C api to Rust wrapping hand wavy Connection::open (that probably has to wrap the same C api in unsafe block) without showing what is hidden there... As C++ ugly-but-short-code would be: int main() { std::unique_ptr< std::string, decltype( []( auto* db ) { sqlite3_close( db ); } ) > conn { [] { sqlite3* db; sqlite3_open( "abc", &db ); return db; }() }; if( conn ) { //do some connection work } } Btw async/await comes from F#/C# so Microsoft, to JavaScript, C++, Rust... So credit for the feature is not in JS land...
I don't think you need to create a Vec of Boxes, since Vec already holds a pointer to its heap-allocated data. There's already one pointer indirection, no need for a second.
A Rust Bootcamp sounds like something that could motivate me to start learning rust in earnest. But when I hear Programming "Bootcamp", I become skeptical of how useful it would be because so many of programming bootcamps are bad or just plain scams.
📝Get your *FREE Rust training* :
letsgetrusty.com/bootcamp
it doesn't work!
@@sinistergeek yeah, immediately after he released it, he made it paid for.
While I don't mind if you charge for something, I think it's real scummy to advertise something as a "free training" then on day 3 make it cost! (and advertise a 'discount').
It's not free. It's 4 bullshit videos of just him talking, and then you have to pay for the actual bootcamp. Good news, there are many free rust training programs that are way better than this.
Employers do not care about for profit online "bootcamp certificates". If you pay for this, you have been scammed.
Best way to ruin your reputation
technically, async/await came from C# not javascript. and even more, async keyword was introduced in F# in 2007.
sorry for the 3 week necromance, but, async/await is just syntax sugar for monadic do notation, which itself is just syntax sugar for a series of monadic binds. I don't know what language was the first to introduce the concept of monads (whether by that name or a different one), but Haskell was definitely the first to adopt do notation as imperative sugar syntax, which happened in the early 1990s.
@@ZSpecZA heh. and then mondads came from math. does math have async/await? if so, then good luck to create web server in algebra.
of course, it's a syntactic sugar. but it's VERY convenient and creative syntactic sugar. sometimes form matters.
@@s1v7 do notation has a very similar form and is arguably more flexible, imo
Plus, the do-notation is much more general. It doesn't just work for "Futures" but also "Result", "Option", lists, vectors, .... It's really amazing that there is an underlying pattern at all.
I came to comment the same!
8:53 I may be wrong, but the Box in Vec is unnecessary and will cause extra allocations. You can put the Employee directly into Vec, since Vec already allocates them on the heap, so you won't get a recursive enum.
@@mevishalcoder Well, that's exactly what Vec does. It stores the actual elements on the heap, and just a pointer, size, and capacity on the stack. So, Vec is just 1 pointer and two numbers on the stack, which is fixed-size afaik.
You are right.
I think OP was thinking about generic types "inheriting" traits. For example, let say we want to get a vector of any type which implements Send, then you have to do Vec because you cannot put a dyn type in Vector, so Vec would cause an error.
But that's not necessary for enums because enums are a compile time defined types.
@@iamgly yeah, that makes sense
@@mevishalcoder what? Vec is fixed in size, you can think of Vec as a Box, so you can totally have a struct with a Vec in it.
@@Baptistetriple0 According to the Rust book, that's not true. Vec is definitely not a Box, though you will have a fixed type(s) in a vector.
One design choice by Rust that I consider a feature is the fact that unlike C/C++, variables are const by default and your must explicitly make them mutable. This helps to both guarantee that certain values don't get changed where they shouldn't, and also makes code easier to read because in C many folks forget about const, but in Rust mutable tells the reader of code that the writer of the code specifically wanted this variable to be mutable. I guess you sort of cover it in talking about borrowing but calling out C specifically might catch more people's attention.
I actually really like this too. Makes everything so much cleaner.
It is like F# and I also like this. Unfotunately not used much.
6:00 Its not magic. Rust uses the Drop trait for an equivalent to C++ destructors. In your example, the Connection type simply implements this Drop trait to implement the logic to release the connection. It would be identical to having a C++ class called DatabaseConnection with no destructor that stored a Connection class that had a destructor to close the connection.
Right, I was debated on how much detail to include. Thanks for additional context!
A perfect example is Box or Rc. You don't need to remember to deallocate the memory (malloc/free or new/delete), the destructor does it for you.
Looks like you really needed to come and show off, didn't you? Do you want us to call you smart?
@@jayp9158 No need to get mad. It's just a correction. Drop is something you learn at the beginner-intermediate stage, and this correction of "magic" to Drop can help beginner developers understand how structs work and how to automate and clean up their code.
@@jayp9158 it's not that deep. This is literally just RAII!
I have to say, cargo is also my favorite feature. Just seeing all this green like instead of the crappy mess of makefile is really satisfying. And I feel like is always succeed execpt when some crate relied on C lib
I think the RAII with the sqlite example could be implemented as an std::unique_ptr with a custom deleter(lamda that calls sqlite3_close()). Using a unique_ptr would also prevent unintended copying of the connection and having multiple objects pointing to it.
Was thinking the same thing. But I guess it was not shown in order to not overload the video with too much C++ side-information
@@WolfrostWasTaken+ it makes rust look better
@@plaintext7288 Yeah but if you know C++ you know we been applying this pattern for years lmaooo
That’s the thing with c++ - if you do it right (modern c++ paradigms), you don’t have to worry so much about manually managing memory and nitpicking the performance of trivial operations. But people are still vocal about how dangerous and verbose c++ is (well that last bit is still fair point)
in your C++ example about zero cost abstraction, the std::max_element function return an iterator which is the first occurrence of the maximum value found between the two iterators you gave.
if you try to read the resulted iterator directly, yes, it will be an undefined behavior. what you have to do instead is to compare the resulted iterator with the end iterator you gave in max_element (which is not inclusive). if those two are equal, it is like a None optional in rust : no max value found because of empty vector for example.
At 6:24, the point you're making about the no-aliasing rule is correct of course, but the example you're giving of invalid code actually compiles without any errors. It would've been an error in an early version of Rust, but since "nonlexical lifetimes" were added, the compiler has been smart enough to see that references you never touch again can be short-lived. To get code that's actually invalid, you need to mention one of the shared references again after the mutable reference was created.
You just gotta pretend the comment below the definition of those variables actually has some relevant code.
i just want to say that async does not come from JS but from C#
Microsoft either invented async-await or were the first to mainstream it. Then other languages adopted it.
I just want to say that async does not come from C# but from F#
@@Jaood_xDI saw the thumbnail and thought I don’t think JavaScript invented that then looked it up and found what you just said and was just about to comment it when I saw the comment you where replying to and was about to correct them when I saw you correct them 6 min before I had the chance
@@patfre 🙂
Apparently Don Syme has stated that his implementation of asynchronous workflow came from a paper wherein a monad for concurrency was designed in Haskell. Not part of an official release of the language, concurrency was a big topic in the 90s.
F# was the first language to implement the famous keywords and asynchronous workflow.
5 years later, C# was the first mainstream imperative, OO language to implement it.
So, kudos to the Microsoft teams, haha.
Also, F# is an incredible language. Very underrated, check it out if you like functional-first programming that still lets you fall back on your bad habits if you want.
2:26 Calling max_element on an empty vector is not undefined behavior, it returns an iterator pointing at the past-the-end element. Dereferencing that past-the-end iterator is UB, so your point stands. You still need to rely on the programmer to account for both cases, meanwhile in Rust the compiler forces you to account for them.
Reading past the end does result in undefined behavior.
Yeah. That's like what Rust says that manipulating pointers isn't UB, dereferencing one can be. However we both know just because one requires unsafe and the other doesn't, doesn't mean that the UB can't be introduced by the safe counterpart.
@@VivekYadav-ds8oz I think people have trouble understanding what "undefined behavior" really means. Undefined behavior is basically saying that "if you do this thing, we make no guarantees about what would happen." Anything can happen when you read past the end of a vector in C. Nothing could happen, it could seg fault or your HD could get formatted. There is nothing that says "This is what would happen if you read past the end of a vector". The behavior is not defined.
2:29 - No, calling max_element on an empty collection is perfectly defined behavior, and is handled similar to your Rust example. The returned iterator will be numbers.end() and you need to check for that case, similar to how you check for None in your Rust code.
The undefined behavior comes in because you unconditionally dereferenced your result without checking. It's the same as if you unconditionally called unwrap() on the result in your Rust example.
I really wish Rust people would be more honest about how C++ works and not try to magnify what is and is not undefined behavior. I think Rust is good enough at what it does that you don't need to misrepresent C++ to show Rust's strengths.
Speaking as someone who is new to Rust, and has only done C++ in university...
I think the point is that in Rust, you have to explicitly use unwrap() to possibly cause problems, and if you don't... if you just forget to handle that case, the compiler forces you to handle the empty case.
In C++, you get the problematic version by default, not the safe one. And it compiles too.
Also, in Rust, if you unwrap() when you shouldn't, you're causing a panic, which usually means aborting, whereas in C++, you'd get... idk... srgfault maybe... and get it not immediately on calling the max, but when you later try to use that max.
@@boenrobot My issue is that it wasn't presented that way. I believe what you say about Rust is true, though I'm not sure if you get an error or warning or anything about an unchecked unwrap, but if not you will certainly get a runtime panic rather than undefined behavior.
However, the C++ side was completely misrepresented. What was claimed to be undefined was in fact defined if one used the interface properly. Crank up the compiler warnings and I believe some compilers will warn about it, or if not, a linter definitely could. Compile with checked iterators and you'll get a more controlled runtime crash rather than undefined behavior. And if it is still a concern, one can wrap (or reimplement) the standard library to use features like std::expected or any of the other similar types people have written over the years.
And that's where I think Rust can make a strong case without misrepresenting what C++ does.
Is that as reliable as what Rust provides? Maybe once it is fully set up, but it is more work, possibly less cross platform, dependent on the quality of implementation of the build tools, and in no way a guaranteed available thing, whereas that stuff is built into the language in Rust and available everywhere Rust is. Having that stuff available out of the box is very nice.
Is Rust a good enough improvement to make it worth switching? For some domains, absolutely. For an experienced team with their environment set up to catch those sorts of errors, it might be a marginal improvement. There might be other things C++ brings that Rust doesn't have yet. But that can be improved over time.
I think Rust advocates do themselves no favors when they seem to only argue against C style C++ or assume one must mess up in C++ but the Rust coder will never write a similar error. Or when they oversell exactly how much safety Rust can actually provide. But for a lot of those things, what Rust does provide is still an improvement even when you deal with C++ in the best light. Some of it will get folded into C++ over time, but some of it can't be.
5:20 It is utterly unclear how one can compare C++ to Rust without ensuring an apples-to-apples comparison. If you are employing a type in Rust that can automatically close connections upon destruction, why would you not utilize an analogous type in C++? Conversely, if you prefer using a low-level API in C++, which necessitates closing connections manually, why would you not employ the same low-level API in Rust?
Simple, because he needs some validation and ego stroking, poor guy can't find a rust job and needs to justify to himself that rust is totally the superior most perfect and elegant language that has ever existed in all of human history and that every other single language on planet Earth does things wrong. Rust just uses magic bro, what are you talking about? Destructors? oh we call that dropping here, and it's done automatically like it's magic! wait, you mean to tell me that every other language with destructors also does it like that? SHUT UP, RUST IS DIFFERENT BECAUSE DROP IS MAGIC!!!!!
Bias is different from intentions, so maybe he didn't realize this while planning the content, arranging it into an order, creating video content, and producing the video. But, if he sees your comment which explains this clearly, maybe he will notice for the future.
because he does not know C++, I noticed the same thing.
Nice drill-down of core features (and how they came to be)! Already "signed up" for the Bootcamp, hope for its release SOONER than later! :) good work @Bogdan!
and npm is not the first packaging system in programming languages...
Thanks for a very informative video. I especially enjoyed how you explained where some of the language features originated and how they are implemented in rust. The originators and community really put a lot of thought into improving the great features of other languages and making a first-class language simple to use.
Most of these examples have been simplified to the point of being misleading. The C++ example for finding the maximum value did no error checking while the Rust example did. The Rust RAII example did not actually close the connection in the DatabaseConnection drop method because I assume Connection already does that automatically. What's the point of that example then?
Async and await wasn't taken from JavaScript. The syntax originated in F# and was quickly adopted in C# as well. From there, it spread to other languages like Python and Javascript, and eventually Rust and C++.
the only bad thing about rust is the foundation
Could you explain why is it so!!!
@@abhishekyakhmi Probably because of their "all programming languages are political" bs
fr, I hate the foundation.
Job market isint that popular last time i checked
@@birdbeakbeardneck3617 yes, but it will eventually become because rust is still in beginning phase...
Hi great video! May I make one suggestion, which is to annotate the timestamps with actual descriptions instead of "Feature X".
Yeah, using the feature titles would also improve the SEO of the video, so more people looking for Rust videos can find it too
8:43 why is Employee Boxed? Shouldn't wrapping it in Vec already stop the loop as it'll be behind a pointer/reference?
I extended some code last week using traits and at one point the compiler forced me to use a Box dyn thing. It wasn’t obvious why but it might be a similar thing here. But I’m fairly new to Rust.
@@kevinmcfarlane2752 I assume what happened there was that when you use a `dyn Trait`, it needs to be behind a pointer. This can be done with Arc, Rc, Box, or a raw pointer. In this situation, Employee is an Enum IIRC, so it doesn't need the Box.
There is a problem in your RAII examples too :
You are telling Rust is better than C++ because ownership is integrated in the language and you don't have to call for a close function to quit the database. But that the same in C++ : Connection may implement Drop to close the connection on its own, like C++ does when the scope goes down. You show a full raw implementation in C++ but not in Rust, that's unfair to compare those code.
Agreed. The Rust example is taking advantage of some other Rust crate (looks like rusqlite) to do the cleanup, while the C++ example is calling the upstream SQLite C API directly. A more direct comparison might have the Rust crate use unsafe code to call the same C functions.
That said, the C++ implementation here is also dangerously incomplete. The default copy constructor and copy assignment operator both need to be deleted, or else a simple copy of one of these objects (like passing it to another function by value, or assigning over it) will lead to a double-free and probably a nasty crash. At the same time, an explicit move constructor and move assignment operator probably need to be added. (Maybe it would be better to do all this by just wrapping a std::unique_ptr with a custom deleter?) An unsafe Rust version has its own pitfalls to watch out for, but there's no equivalent of the Rule of Zero. The closest mistake you could make here in Rust would be to explicitly derive the Copy trait, but that doesn't happen by default.
I think this is because in Rust, we only define drop function when creating primitive type and this never happen. And probably we are obligated to implement it in that case
Edit: forget this, I was wrong
I don't think he's saying why Rust is better than C++, he's just explaining which languages played a part in influencing the design of Rust.
I think you have slightly confused the term zero cost abstraction with the zero-overhead principle. Zero (runtime) cost abstractions mean that when you use abstractions, they should not add any runtime cost versus doing it without the abstraction i.e. unique_ptr has the exact same runtime performance (better in some cases) as a raw pointer, while wrapping it in the abstraction of a smart pointer. Zero cost abstractions are why people like Sean Parent argue for never using raw loops, because we can create abstractions that add zero cost while making the code a lot more manageable and readable and can even allow the compiler to better optimize. It should go without saying that you should never incur a runtime cost for things you aren't using; that isn't because of zero cost abstractions and really overlooks how powerful they are in C++ and Rust.
Async and Await is actually owned by C# and not Js.
NPM: 2010... PyPi: 2003... PECL: 2003... PEAR: 1999... CPAN: 1995. Yeah, Node didn't come up with the idea of the library package manager, not even close.
It's more like the creator of this video doesn't understand anything
12:29 In javascript, .catch handles the error and whatever you return becomes a resolved value. In this example, getUserData(userId) can now NEVER reject, because we're eating the error with the console.error, I'd consider that a bug. (It either resolves with the json or resolves with the return value of console.error, which is undefined).
Instead you would want to do the following
.catch(error => { console.error('Error:', error); return Promise.reject(error); } ) // throw error is also acceptable.
This highlights why people moved away from manually chaining promises with .then and .catch because there are some funky gotchas you have to watch out for.
However in production code, I'd recommend just not catching the error at all in this function, and having one single catch near the top of the call stack, but that would obviously be a bad example for a video.
@@tckswordscar That should be done in pretty much all languages. Avoid handling errors right there and then, unless you need to add fallback behaviour which doesn't break the flow and UX.
I would rather say that ADT and pattern matching are adopted from OCaml (when traits - from Haskell), but since OCaml is not so known as Haskell, the latter sounds good as well ))
Thank you for the video!
Awesome coverage of really cool features. Looking forward to what's coming next. Keep up the great work!
You can clean up the nested promises by using promise chaining instead, but yeah async/await is very nice syntactic sugar
Can you elaborate more on 2:50 when you said "There are runtime checks in other languages for this, but Rust is fast"? How does rust avoid runtime checks if it doesn't know in advance the result of max element?
i rmb quitting rust twice. it was not easy to learn. thanks for this insightful video
Hyped for the camp! :D
Great video, just wanna point out that did "crates" really come from nodejs npm (2010) or it's idea come from way back with the creation of apache maven (2004) a dependency manager for java applications which also helps in build, test and run applications ?
Fun fact: graydon hoare recently wrote that he didn't want async await in rust instead he wanted a go style concurrency built into the language but others didn't agree to it
i also am fond of go's style, seems way more flexible. however i have to wonder if it's possible to put it in rust while keeping it zero cost
That will probably make targeting no_std much harder or impossible
Goroutines and channels in Go are such a breeze to use! I love Go's simple approach to concurrency, almost like writing single-threaded code.
const p0 = Promise.resolve()
const p1 = p0.then(f1)
const p2 = p1.then(f2)
const p3 = p2.then(f3)
const p4 = p3.then(f4)
you don't need async/await to do not nest promises in js
Yes xd
Instead of callback hell, we now have promises hell
Honestly, I think C++ is doing just fine. The examples are just aligned towards Rust. No one is using makefiles directly with C++ these days either. Rather CMake in combination with vcpkg (for package management), is a breeze. vcpkg search package, vcpkg install package etc...
6:06 I understand that the memory of the connection object is freed, but where does rust close the db connection? Doesn't it need to send a signal to the db (or, at least, our local OS) that we don't need the db connection anymore? How do you clean up after yourself in rust?
That example is bad. The C++ example used a connection handle while the Rust one used a higher level connection type, where that underlying type should still implement that exact same close call for it to work.
Your type should implement the Drop trait to be exact, which works similarly to a destructor in C++, and close the connection there.
@@gabiold Thanks! After poking around a while, I've found Rust's types can control their memory allocation in really precise ways. I'm loving it.
3:00 in Python collections can't change during `max` evaluation(because of GIL), but on the other hand there can be any type of object so Python checks for types(and of course all data in Python is in heap memory)
thats why it is not zero cost abstraction, but it is not about `max` function but about the python itself
C++17 has sum types with std::variant, you just replace the rust's `match` statement with c++'s `std::visit` function
That looks utterly horrifying in code, probably instanciates 100 templates and makes your compilation time slower and also it is not a feature built into the language but a standard library stuff and thus it can confuse the compiler and make it not optimize correctly.
If you are using C++ use unions, anyone who tells you to use std::nonsense instead is probably a psychopath.
True but variant syntax is awful in C++.
Async/Await are from C#. Then it had been omplemented for JS
I go ahead and add another feature that Rust inspired from C++. Move semantics were introduced in C++11 to address the issue of excessive copying, allowing for the "moving" of heap-allocated objects from one owner to another. However, C++ does not provide safeguards against using the source owner after the move. In contrast, Rust adopted move-by-default semantics, as opposed to C++'s copy-by-default approach, and implemented guardrails to prevent the use of the source owner after a move operation.
Thanks for the video ❤
what's the font being used for the titles? it's very nice, and reminds me of that one you see everywhere in japan / japanese games.
At 6:30 you say that we would get a compile time error when introducing a mutable reference which is not the case. This code snippet actually compiles, the error will only be triggered if we decide to USE the mutable reference while another immutable reference is "active" (or vice-versa)
I love your short videos. I will buy your course even if i do not need it!
I have no clue about Rust, yet, cause I'm just watching maybe a third video on it, but shouldn't enum Employee's Worker have manager: Employee instead of string??? 8:33
At 8:50 why are subordinates of a manager declared with a type while the worker the manager is just a string?
Obviously an author wanted to show that manager is able to control another manager(s) too. But this implementation looks like any manager doesn't know who is his/her boss 😀
5:02 C++ has defaulted, constructors and destructors too and has had since 2017. Also based on what I heard of rust anytime anyone starts doing anything anymore complicated than basic IO They start using unsafe blocks which , and this is how I have had it explained to me, make it basically writing C in rust syntax...
nice video! Instead of parroting what you read yesterday, there is perspective and vision. Thanks
Isn't the "Box" at 8:58 unnecessary?
Great video. I think the async part for JavaScript was not entirely true though.
JavaScript is not exactly single threaded. It uses libuv for executing async operations in different threads.
By default JavaScript uses 4 threads (defined in libuv) for executing these operations (crypto, disk I/O, dns lookup and ...)
async is first introduced by C#
Although what you described is actually related to NodeJS engine rather than JavaScript as language, you’re right that JS does support threading with various implementations of worker threads. I don’t get why people keep saying it’s single threaded, as worker threads have been around for a while now
This is such an amazing video! Loved how you compared each language and took time to look at the pros and cons.
I just wanted to add that JavaScript async/await was inspired by C#!
Super excited for the bootcamp, thank you for all the great resources!
A lot of 'what's borrowed from where' is misleading
async\await was first introduced in F# [okay, it's not as popular for your viewers], then picker up by C# [could've used that] and then by Haskell, which you are already mentioning before, and only after that in TS & JS.
package managers obviously existed before NPM, Maven being one of the first in regards to programming languages, although I'm not sure if it was the first
I did not know about F#, but definitely learnt async/await in C# in early 2000s.
First package repository should be CPAN. Probably Perl did not have something like npm at that time. At least Ruby got RubyGems around 2003. Even Gemfile uses some DSL than JSON/XML/YAML, but it's already a decent modern package manager with lock-file support.
I’ve dabbled in F# a bit. I’ve not seen that syntax. Not saying you’re wrong though. I do know that C# has copied from F# in other areas.
I find it funny that the JS async await is used as inspiration despite JS being single threaded. As a long time frontender that has experienced callback hell, I understand that the web is very event driven and that calling external online resources means that you often need to avoid blocking code that freezes the website for the user.
With promises I think I would have written your example by returning the nested promise with the other values you acquired and then destructure the values in the next "then" closure.
This would look like:
fetch(...).then(
val1 => fetch().then(val2 => ({val1, val2}))
).then(
vals => fetch(...).then(val3 => ({val3, ...vals}))
).catch(console.error)
This way you propagate the error to a single catch, you'll never need to nest deep and you still have access to all the returned values. Although now I just use async await.
c# invented async/await, not JS.
@@RicardoLacerdaCasteloBranco this guy knows
@@RicardoLacerdaCasteloBranco F# inveted async/await, no C#
How do you even use Rust, "async/await", "object acquires resources".
There isn't CPU instruction "acquire resource".
I'm just curious, do you memorize it like some tabletop game, like "it's a rustonator of second level, it can do cppnization"?
Dijkstra was right.
I'm considering writing audio applications in Rust, but this would require an audio buffer which my code would be continually writing to while the DAC continually reads from that buffer. I'm pursuing real-time audio synthesis, with being able to change synthesis parameters while the output is being read. Would the Unsafe option be enough?
12:55 it should be pretty easy to refactor that into more readable code that still uses promises.
13:18 what? you can use promise as chain, this is incorrect example :(
Good job you did give credit to C++ and even provide idiomatic example in first feature of zero cost abstractions. However, in second one there is unfair comparison.
C++ database was created on heap with new. However one could create one on stack and have as that simple code as Rust had.
For more complex behaviours, you also should provide Drop trait in Rust for some cases. Which is equivalent to destructor in C++.
Eh, more or less unfair. C++ wouldn't automatically call the destructor of a database if it was inside a class that didn't explicitly have a destructor.
@@henrycgsTo my knowledge, so wouldn't Rust if no Drop trait was specified.
@@danielmilyutin9914 in Rust, all members of a struct get dropped when the struct itself gets dropped. In the example shown in the video, you wouldn't need to implement Drop, since we can assume the sqlite object cleans everything up upon dropping (if it didn't, that would be a bad/wrong implementation).
You only ever need to implement Drop for very low level stuff. For instance, if you're creating a database handler from scratch, perhaps you keep some numerical file descriptor that you use to talk to the database. Then, you do need to manually close the file using the fd, and you'd do that by specifying drop.
@@henrycgs Thanks for clarifying. But anyway good-designed C++ database library class would provide destructor on its side. And high-level database handle/object will be limited to scope. User must not write/call destructor in this case. Either them uses it in scope of function or in struct/class. This is called RAII. And Rust borrowed this concept in its language design.
For me this looks absolutely equivalent to drop.
C++ provides special member functions - destructor included - automatically but user can redefine if required. If ones class consists of good-designed objects and no low level resource handling is required, destructor is not needed. Only wrappers of low level resources should have destructors in good C++ code.
Upd: And anyway all member destructors will be called in struct/class in C++ . If user mixes low-level and high-level handles in one class/struct (and that is quite bad design on him), high-level handles no need to be mentioned in destructor.
@@henrycgs Sort of incorrect. C++ destructors are always called, for every object, forever. Even objects that don't define a destructor already have one - the default destructor. By nature of RAII the default destructor calls other destructors in order. So, if you have Class Database {} with a destructor and include it inside another class which does not have a destructor, the Database destructor will still be called every time. This is why STL containers like string can be included in your own classes with no work or management. The one exception is deleting the destructor. This is basically never done - because an object without a destructor can never be created on the stack or with new.
Want C# the first ever language to use the async keyword even before javascript? 🤔
C# first use of async and await syntax. first use of async is f# (inspired by ML ideas)
First to use that exact system, but not the first one that introduced async conceptually. Moreover, async blew up in JS much more than it did for C# at the time, so it's very possible Rust inspiration came from JS and not C#. Whoever was first doesn't matter in that context.
JS copied it around 2012 when every other language did. Was around long before that.
JS is an interpreted scripting language. Not exactly the place to find CS innovation.
>rust borrows those features
does it borrows it mutably or immutably though?
15:30 parallelism in JavaScript is not an illusion. There is a thread pool that is managed by node/browser runtime and threads from it are used to run asynchronous IO operations in parallel.
Yes: it's parallel threads pretending to be one thread pretending to be parallel threads 😅
I’m almost sure async/await and [attributes] were taken from C#
Which in turn were taken from another languages too. Async has a long history that dates back to '92. It's kinda meaningless who was first anyway.
@@Tiritto_ alot of programming concepts are old. What we are talking as “first” here is mainstream. For instance, Java didn’t invent OOP but it sure made it mainstream.
@@metaltyphoon But then if we define "first" as being the one who pushed the concept into the mainstream, then I would argue that JavaScript was indeed the first one.
@@Tiritto_ but it wasn’t C# async await is where JavaScript copied from. C# is mainstream and denying that means someone is in la la land
@@metaltyphoon I believe that the one who truly is in a "la la land", is the one who declares a principle and then immediately derails from it.
2:30 no, it would return the end iterator you supplied it. std::max_element never invokes undefined behavior apart from if the iterator types do.
11:54 I think Rust and JavaScript stole async/await from C# 😂
Great video btw, I know I’ll be referencing it a lot in the future!
And c#(in 2011-2012) stole async syntax from f#(2007). But await syntax I think that is from c#
@ C# and F# are both developed by Microsoft my dude.
@@rastaarmando7058 the original commentary not has any mention of Microsoft, only c#
Rust stole it from JavaScript, that stole it from C#, that stole it from F#, that stole it from Haskell, that stole it from ML.
functional languages like haskell had generalized async/await long before things like f# came around
@18:28 Lol, guess we don't get a macro example for Scheme 🙃
3:06 Comparison with Python is quite confusing, since max() function actually increase performance compared to for loop.
Looking forward to your bootcamp, great work so far!
Bootcamps are for idiots.
can macro handle a function that returns a value?
Because i want to write
std::io::stdin().read_line(&mut String&::new()).unwrap;
To prevent the terminal from closing
Sorry if my comment appears multiple times, it looks like it keeps getting removed by youtube and I presume it is because I linked cppreference.
You are mistaken in 2:26.
From the article about std:max_element in cpprefrerence, passing an empty range to `std::max_element` is not undefined behavior. It returns the second iterator that you passed to it, which points to nothing. The undefined behavior part is dereferencing the iterator, which is conceptually identical to naively calling `unwrap` on the result of the call to `max` in rust. It is the same kind of error.
Rust gives you additional value here because it would panic instead of invoking undefined behavior which, in my opinion, is almost always better.
Both languages give convenient facilities for checking whether it holds a value or not, however IMO rust's is better.
5:40 doesn't the lib for the database have a destructor? how does it tell to the database that the connection should be closed?
Rust has a Drop trait, which is ran right before a variable is dropped/freed. This would most likely be implemented to close the connection
@@tabiasgeehuman Thank you!
Yes, the database has a custom destructor. You can implement custom destructors via the Drop trait. It has a "drop" method that runs just before the object is destroyed. Rust runs the "drop" method recursively on the struct itself, all its fields, all fields of its fields, etc. You can never "forget" to destroy something, even if it's deeply nested within composition hierarchy.
@@KohuGaly kudos to this comment! TIL that Drop needs to be ran recursively for all things to actually work out / for the memory to be freed :)
I assume when you Derive(Eq), it does those equality checks recursively across all fields too, right?
Good video, currently learning Rust full time
Rust is like the Frankenstein's monster, a masterpiece that combines the best from the most important and powerful technologies ever created.
What a great video, buddy.
Except that C++ code (and explanation of RAII) is not exactly good/idiomatic and thus not comparable to Rust's hand wavy edition...
Async/awayt comes from F#/C# and not JS...
Etc etc etc
This commercial has the same problem as most of the Rust "community" and language in general... Not taking enough time to know other languages well enough to even know how to compare between them...
Rust Foundation is like the Doctor Frankenstein
You mention zero cost abstractions a few times in the video, but I am not fully clear as to how we get zero cost - can you please explain with some concrete examples in one of your future videos or point me to some article that explains this concept
The idea of zero cost abstraction isn't that the code you write will magically work in zero time or anything like that. Rather, it is two things:
1. Compared to doing the same feature manually in C or ASM or other low level language, the abstraction itself should not have any additional cost, but should work just as well.
2. The mere presence or availability of the abstraction shouldn't cause overhead or performance loss in code they doesnt use the abstraction.
E.g. C++ has virtual methods, and they are often considered "slow". But compared to a C struct of function pointers to achieve similarly polymorphic code (think struct SDL_RWops or struct sqlite3_file compared to a virtual base class in C++), using virtual methods usually has about the same performance. And if you don't need the indirection, the fact that C++ provides virtual methods doesn't slow down member functions that aren't virtual or add anything to classes that don't use virtual methods at all. The _abstracrion_ has zero cost.
In reality, sometimes there is a cost, and sometimes the abstraction enables better optimizations that give a negative cost. Being aware of the tradeoffs of what the compiler is doing and measuring are still needed, as it isn't magical.
@@oracleoftroythanks for the detailed answer. So, if I understand correctly, you get to use a high-level language feature which saves you from having to deal with all the internals of something familiar, but you don't pay any extra overhead at run-time because of the abstraction. Right?
Rustlings great place to start, feels like your talking and working with the compiler after a while🤖
Why did I choose to become developer.. 🤯😭😭
12:52 That's a terrible example. Even before await. Promises were made to solve callback hell by being able to properly chain the .then().
The example can actually be properly rewritten as...
fetch('blah')
.then(res => res.json())
.then(res => fetch(`blah{res}`))
.then(res=> fetch('on so on'...));
Of course awaits are better and way way readable
17:44 does `name` need to be mutable here? 🤔
18:16 ahhh that's why :D
Excellent job, Taras! It was the best overview of Rust I've ever seen before! It's short and deep at the same time. It's almost perfect. I'll shurely put it into my favorite list.
I'm curious how you'd replicated Javascript's eager Promises if that was the behaviour you desired? So that you start the async operation independent of where in your code you await the result of it.
I think you can achive it by using tokio's tasks. You could spawn a new task with your Future, get the task's Future and await it whenever you want. That's because tasks will execute when spawned, no need to await them in order for them to do any work.
Yo Bogdan, how useful and practical is rust for embedded software development? You wouldn't have any resources on this?
It's usable. There are hardware abstraction layer (HAL) libraries for most of the commonly used microprocessors. Though the ecosystem is somewhat immature - you may run into lack of support once you go beyond basics.
Thanks guys! I'm absolutely brand new so I'll stick with what I'm learning C, hopefully down the road I can jump to rust
thanks for sharing this comprehensive video
Great overview! I might pick up Rust at some point.
oh hey a video from the official -rust- Rust Foundation!
Good to see your eye is better now 🧡
Amazing! Thanks for the contribution!
How similar is Rust Future to Scala Futures?
Who created the bootcamp?
Extremely good video, thanks 👏
This was a majestic video 🔥
This was truly your best video so far. Keep it up!!
very well delivery!
I know that this is a Rust (course) commercial but still it rubs me the wrong way if C++ is poorly compared to Rust... Es
RAII means "if resource is acquired then you get an instance, otherwise you don't get the instance" - that's the "is instantiation" part - so idiomatic c++ implementation would actually be (example both for using exceptions and non-exception code):
class db_connection
{
public:
db_connection( std::string_view db_name )
{
sqlite3_open( db_name.data(), &db_ );
if( db_ == nullptr )
throw std::runtime_error( "connection failed" );
}
~db_connection() { sqlite3_close( db_ ); } // no need for nullptr check here as it is instantiated by RAII definition...
static std::optional< db_connection > make( std::string_view db_name )
{
sqlite3* db;
sqlite3_open( db_name.data(), &db );
if( db == nullptr )
return std::nullopt;
return db_connection( db );
}
/* more functions */
private:
db_connection( sqlite3* db ) : db_{ db } {}
sqlite3* db_;
};
int main()
{
// without exceptions
if( auto conn = db_connection::make( "abc" ); conn )
{
//do some connection work
}
// with exceptions
try
{
db_connection conn{ "abc" };
//do some connection work
}
catch( std::runtime_error const& error )
{
std::format("error: {}", error.what() );
}
}
And it's not nice to compare C++ wrapping C api to Rust wrapping hand wavy Connection::open (that probably has to wrap the same C api in unsafe block) without showing what is hidden there... As C++ ugly-but-short-code would be:
int main()
{
std::unique_ptr< std::string, decltype( []( auto* db ) { sqlite3_close( db ); } ) > conn
{
[]
{
sqlite3* db;
sqlite3_open( "abc", &db );
return db;
}()
};
if( conn )
{
//do some connection work
}
}
Btw async/await comes from F#/C# so Microsoft, to JavaScript, C++, Rust... So credit for the feature is not in JS land...
I don't think you need to create a Vec of Boxes, since Vec already holds a pointer to its heap-allocated data. There's already one pointer indirection, no need for a second.
Already subscribe to the bootcamp!!!🎉🎉🎉
From what you described, there is not much difference in Rust trait/generic vs using virtual abstract classes (interface) in cpp.
wait what? async is originally from C# not JavaScript.
can you start a new playlist that solve leetcode problems in rust? atleast once in a weak?
A Rust Bootcamp sounds like something that could motivate me to start learning rust in earnest. But when I hear Programming "Bootcamp", I become skeptical of how useful it would be because so many of programming bootcamps are bad or just plain scams.
Just learn rust on your own, start with official rust learning resource and do projects in the meantime
you should use a shared_ptr for the sql example