ERRATA - 0:43 This is not how format strings look - sorry! It compiles but doesn't do what I wanted. - 1:07 misspelled "California" - 2:15 duplicate variable name "rand_int", it should of course be "rand_float" - Because randomising sections can cause duplicates, your searching will find the FIRST instance of the section, not necessarily the one you want. The fix for this is left as an exercise for the viewer, and future me, apologies! - 7:00 thanks to everyone who said that `iter.find()` would be perfect for this job! - Thread panic on station breakdown reported by @Mitch557. Logic bug in my game loop, but unrelated to the type system demo github.com/0atman/noboilerplate/issues/9
A favorite pattern of mine is the free generic. Say you have several objects with ids that are all the same underlying type, such as Uuid. You can have a type, say TypedId(pub Uuid, PhantomData). This is generic over your structs and only holds PhantomData for the free generic. It is no larger than a Uuid, but now, you can't mistake a Station id for a Section id!!! This is better than `StationId(pub Uuid)` because you only have to implement things once while still getting type safety!!
@@ArrekinWorks Absolutely! In the example, I've also included a link to a project where I'm using this, in case you want a "real world" example. Note: RUclips doesn't like me posting links, so go to the Rust playground and add gist=34f765f98445ca4c2e8da6fbab6a53f8 to the URL
@@tylerbloom4830 Amazing, Thank you! I'm still early in my days of learning Rust but I already hit this issue with having one type as id for different structs that should not be treated as one type and so far I used structs `Alias(pub IdType)`. I didn't know about PhantomData, so thank you for the larger snippet :)
@@ArrekinWorks No problem! I was doing the same thing for a while too! What I really wanted was a transparent wrapper. Uuid implements lots of neat stuff, and I'd prefer not to write the boilerplate for it all. But Defer will have to suffice.
2 года назад+148
From "you can use rust to make your life simpler" to "LETS BUILD A SPACE STATION" I like where this is going!
@@NoBoilerplate It does kind of feel like the sort of thing that wouldn't be out of place in std, especially since the compiler could definitely use them as an optimization hint.
Im running into an issue where i watch one of your videos to learn about rust and then i wake up 2 hours later because your videos and voice are so peaceful that i drifted off into an afternoon nap. Thanks for making great content.
I'll take that as a complement! If you want actual relaxing content in my voice, I'd love to know what you think of my sifi, hopepunk podcast, Lost Terminal ruclips.net/video/p3bDE9kszMc/видео.html
I don't have a use case that really requires me to use Rust, but hell, you've gone and made it impossible to resist. I just dropped everything I was doing to watch this and was not disappointed. Bravo!
@@lardosian Yeah, I got quite excited about it as they went 1.0 recently right? However if you look into it, it's an electron clone. A REALLY GOOD ONE, faster and more secure, but you still write your app in web technologies. Not a bad thing, per se, but I'm hardly looking forward to maintaining another js project...!
I felt much in the same way as you do when I was building a personal tool. I found the `From` trait was extremely helpful in defining how to move from one Type to another, because once I'd done it for all possible types I was using, most of my code was just calling `.into()` and it ended up looking like a flow of data transforming from one state into another. A nice side-effect is that almost all of you're logic that handles transforms between Types are easily located in single places (where ever you implemented the `From` trait) so it's all easy to find.
7:04 I'd do something like this: if let Some(sec) = station.sections.iter_mut().first(|m| m.name == section) { sec.active = true; } If you'd like to panic on not finding it, just else-block panic or use match instead. Alternatively, you could just use .for_each(), as Options are iterable themselves. That way you could also easily change from .first() to .filter() if behavior is desired where multiple matches were possible.
To panic after not finding the section you could also do this: let broken_section = station.sections.iter_mut().find(...).expect("Section not found."); broken_section.active = true; Or alternatively this, if you like doing things in a single statement: station.sections.iter_mut().find(...).map(|sec| { sec.active = true; }).expect("Section not found."); But I'd personally prefer the first one.
Mission accomplished. The language is incredible, as I try to show in my videos. start here fasterthanli.me/articles/a-half-hour-to-learn-rust then hit the book, and come talk to the community on the discord server (links on noboilerplate.org)
This video just made me so unexplainably happy ^-^ Both because of the amazing technical aspects and the beauty of rust you showcased, but also because of being reminded of Sev through the topic and your voice after having absolutely fallen in LOVE with Lost Terminal the past few days
This is an amazing guide on how to structure software in general. I have trouble telling my friends and explaining to them how to nicely structure software, and this is an excellent guide. Please make more guides like this because even if only for rust they make for stunning teaching tools.
Love your content! Turn audio compression down, the unnatural sound is diatracting. Device speakers will compress your voice. Users using earphones won't need the compression. Also pushing less air into consonants and more into vowels will do your delivery wonders. You've got a great voice for what you love doing. Keep it up and thank you!
@@NoBoilerplate I'm not sure what he's referring to. The only thing I noticed was a couple mouth sounds, which is fixed by moving farther from the microphone. Wasn't really a bother, though. Great video! I'm excited to see more specific examples of ways rust has empowered you to write software in a more provable and less guess-and-check way.
Note that while Java's type inference isn't as good as Rust's, it can perform type inference based on both sides of the assignment expression. To infer from the left side use the diamond operator: ArrayList foo = new ArrayList(); which is equivalent Rusts's let foo: Vec = Vec::::new(); and to infer from the right side use the var keyword: var foo = new Arraylist(); which is equivalent to Rust's let foo = Vec::::new(); Anyway, nice job on the videos, they're pretty good.
The type state pattern is also incredibly useful here. Things like keeping track of whether a file descriptor is open or closed in the type, or making a builder that requires certain values to be set, is very useful. Admittedly, code that makes heavy use of this looks terrible, though. (For the implementation - the API that results from it is very neat :D)
@@NoBoilerplate Tyestate is the part of Hermes that Rust took to make the borrow checker. When the full thing is built directly into the language, it becomes much easier to read everything. Hermes somewhat over-used it, but it would be nice if more languages incorporated it directly.
Amazing Video !, Thank you very much for giving away this presentation. As a Rust newbie (Coming from high level languages) this kind of explanations are very useful to change the traditional mindset and think in a "Rusty" way. Looking forward for more Videos !. thanks again
Super! I've also come from high-level languages. I have made a few of these, check out the playlist here and let me know what you think! ruclips.net/video/Q3AhzHq8ogs/видео.html
Oh have I got good news for you, there's 9 seasons of me narrating Lost Terminal so far, I'd love to know what you think! ruclips.net/video/p3bDE9kszMc/видео.html
For enums you could use the discriminant function if you want to know if a enum variant is the right variant you are looking for without describing them. You can also use type_name_of_val function if you want the name of the type but don't know the type name. It is in experimental though.
You're right, it's on my bookshelf right now, loaned to me by a friend and ready to read. I will try to do so. I have a recommendation for you: The Spin Trilogy. Incredible stuff.
If he thinks this gives him super powers, wait till he finds out about Bevy's ECS, and how Structs can be used as components. (That is assuming he doesn't know it already, or that he's patient enough to sit trough it's compile times, I have a suspicion that he is.) (I'm also thinking of trying out macroquad, or the rust bindings for SDL. I was even considering building a ray casting engine in Rust, I mean sure a lot of my code will be from some tutorial, that I'd try to translate to Rust, but it might be fun...)
Oh my god, this video is great!! I love you, this will save me a lot of trouble. Please make more videos on how to best write Rust Code. And your voice is one of the most charming ones to listen to, reminds me a lot of Sebastian Lague.
While the possibility to use things in a place before you define them certainly feels convenient, as a reader of other people's code, I have learned to appreciate C and its requirement of linearity. I don't have to constantly jump around the code and nothing can ever surprise me because I know that if it's being used, I must have already seen it by the time it is used.
Though I understand where this is coming from, it's only true in trivial programs. Once you start splitting up things into files, you lose this linearity of behaviour. However, on the whole, I agree that top-to-bottom reading makes absolute sense - the thing is that sometimes I just want to prototype something close to where I'm working, and then refactor it to the 'correct' position later.
With Rust having so many powerful functional language features like immutability-by-default, first class functions, pattern matching, closures, iterators, and so on, surely it seems the safest Rust paradigm is to have no state at all?
Love writing code like that, but now and then it's nice to have just a LITTLE state for some problems. I'm glad Rust is pragmatic so I can get work done - the real world is full of state!
@@NoBoilerplate I guess that is true. I also just learned that Rust does not (I stand to be corrected on this) have good native support for monads, which makes the whole stateless paradigm a lot more frustrating.
These would have been SO HELPFUL for me to learn Rust! Also, I like the little text adventure like style here, because I've been writing a text adventure in Rust (with Macroquad for rendering) for over a year now
3:06 after functional experience I hate using something before defining. All of my programs you should be able to start on line 1 of the first file and read to the last line of the last file (most languages don't have an order of files but I make a note, so it's easier for me to come back to the project) and you will never come across a reference to something you have not seen the definition of. For instance let's say you have never seen the code before and you get to the struct and you see SectionName, you haven't seen that before so you then need to find where the definition is and read it before going backwards back to where you were, and you have to do this for every bit of data that references after the current line which can get annoying having to jump around constantly and it requires more work to understand something. I've had someone say to me that they like to have the main thing first because that's what someone reading will see and they will see the context of what some data is before seeing the definition - but to that I say that you have file names to tell you what is contained and then you have sensibly named data so that when it is all combined you should already have a good idea of how it is structured. The tradeoff is that you will be told about things that aren't immediately used that you will need to keep tabs on but that you never jump around. Also, my thoughts do start in the main body of the program and then they branch upwards until I reach an end and then I write it. I'm used to F# where the first time I run the program it may already be completely finished.
Rust's the same, if it compiles it works! F# and Rust both take after Haskell in that regard. I don't think your 'using something before defining' problems scale well. As soon as you split up your app into multiple files (isn't that most non-trivial programs?) you're going to be jumping to definition anyway? It's a good rule per-file, but imports exist...
Do you need to jump to StationName? It's the name of the station, clearly. If you aren't looking specifically into how names work in the codebase, you probably don't care about the actual implementation. Getting a high-level overview is usually more useful, so I am also of the opinion that the most important bits should come first. Define things top-down, if you will.
If you're following the philosophy of making things easier to read because they're going to be read more than written, it makes sense to order things a little differently, namely putting interfaces and public things near the top, and implementation details further down. Sometimes this lines up, but not always. It feels "neater" to order things by how to compiler will need it, but humans are good at digging into things only when they need to.
@@NoBoilerplate Yep, if you look at the documentation e.g. for std:iter::Iterator, or one of the std::collections, you'll find these APIs where you thought you had to do it with two or three methods, or manually handling an index, but there's a method that lets you do it in one with no index required. Much of the time, clippy will pick these up for you too.
Why do you .expect on section repair when it is easy to assume that section could not be found? Is it really unexpected? Should we panic because of that? I'm asking out of curiosity, is it good approach or should we rather return some kind of error? (Rust newbie here)
@@NoBoilerplate I'll be doing a deep dive before long. I smashed the bell for updates. Oh, Lost Terminal was really nice. It was clever and humorous and well written, and the music was great. I like how you've paired your passions.
0:43 I was wondering if Rust supports compile-time range constraints. What if we want Dogs to only have a maximum age of 30? A dog with age 128 is obviously not a Dog. What if I want the compiler the throw a compile error when a string doesn't match a constant regex? What if I want the compiler to show me a warning when it can't verify that an arbitrary String matches the regex? Raising exceptions when a value is invalid (while its type is correct) is usually done at runtime in most langs. I want to do that at compile-time
Rust does not support arbitrary constraints about "this type but only these values" in its type system, or at least not directly in the form where the original type is clearly still there. However, const generics are very powerful! And so are macros! There is a crate for your use case with bounded integers, fittingly called bounded_integer. It has a BoundedU32 (and friends matching all of the other integer primitives including BoundedIsize) which takes a minimum and maximum value generically. For example, BoundedU8 if you want dogs to be only between 0 and 30 years old. There are some limitations to this approach though, and they don't implement Default among other things. The same crate also has a macro bounded_integer!() which allows you to create a new type which behaves pretty much the same, but macros are slightly more powerful and the type you get out of that will implement some more traits. You could, for example, create the dog age like so: bounded_integer! { pub struct DogAge { 0..=30 } }. I haven't actually used this crate so i don't know why it provides this "struct" form, because the other form of bounded_integer ! { pub enum DogAge { 0..=30 } } should behave the same, but the the enum form makes invalid states unrepresentable by its internal representation, so the compiler can use one of those invalid, out-of-range values to fit other things, so if you might not know the age of a dog, an Option will still be the same size. Also note the existence of std::num::NonZeroU8 and friends, which is basically a BoundedU8. The non-zero requirement is the most common range requirement as zero is a funky value for certain operations, and it can't be the denominator in a rational number for example. I think its actual use case and reason for being in stdlib is pointers though, and using similar tricks the compiler knows that a Box can never be null, and represents the None case in an Option as the null pointer. I'm getting carried away though, as this is for some specific optimization reasons and not for the semantic reason you wanted that "well, dogs don't live to hundreds of years", so let's go to the next thing. String regexes? Well, there's probably a crate out there for that. Several crates do basically the same thing (though not necessarily based on regexes) such as sqlx with compile-checked queries, and one that i've personally used is hex-literal, which i've used to convert color codes "#FF0000" into an Rgb([u8; 3]) for image processing. And if you've ever written any Rust, i can guarantee you've used println!(). The format string has to match a specific format described in std::fmt module-level documentation. The examples i've mentioned for strings all operate on string literals though, but you said "when it can't verify that an arbitrary String matches the regex". Well, no. The compiler doesn't do that. Not for the String type. But what you could do is create a newtype wrapping a String (perhaps even a Cow
@@sodiboo lmao. Don't worry I like learning (and I sometimes also write/talk too much without noticing), I read the whole thing! I already knew about Path strings and OsString, but everything else was very helpful and interesting. Thank you. You should make a video about it, to spread information for other beginners
For your dog example the age would have to be known at compile time and not change at runtime. If that's the case, it could be a const generic and in that case there are hacky ways you can use to make compilation fail if it's not in a certain range, not a power of 2, or something like that.
While I think we all appreciate the hustle of trying to get more views for your projects the length of the pitch for the podcasts is beginning to distract from the content of these videos which are where you are excelling and finding an audience.
I understand, it won't be an often repeated thing. However your options are for me to tell you about my own projects for 30s or to advertise NordVPN for 30s. I am not very interested in nordvpn.
@@NoBoilerplate Honestly, I think it was fine. I sponsor block, but have it set to not automatically skip self-promotions. It gave me the option, but I decided not to skip it. It's not like some other videos where they have 3 minute sponsor segments in their 5 minute videos... Plus, it was at least tangentially relevant to the video, so it wasn't as jarring as these things usually are.
@@NoBoilerplate yeah that’s fair, the deeper integration into the video in this case threw me off because it wasn’t clear how much of the topic was guided by what you think is most useful to know and what was relevant to podcasts
@@notusingmyrealnamegoogle6232 Thank you for your understanding. I will very likely do a few external sponsors now and then, but not freakin' squarespace, I'd rather they be Rust specific companies or tech or something relevant to my audience. The bottom line is that if I can make some money from these videos I can do fewer hours at my day job and make more and better videos for the channel!
You're quite right, every unwrap call you make is a chance to improve the reliability of your program. However, when you are prototyping, like I am here, you can leave a few in, consider them todos that the compiler understands. I use clippy to warn on them, and turn them into errors in my build pipeline!
Nice work, beginning to look forward to these now :) Small feedback though - could you look into putting a pop filter or something similar over your mic, the `s` sounds currently are too harsh on the ears.
@@SriKadimisetty I've got a lifetime of music production under my belt, and 2 years of intensive podcasting for Lost Terminal, so I'm always optimising my audio pipeline! btw have you heard it? ruclips.net/video/p3bDE9kszMc/видео.html
2:15 I'm sorry but I fail to imagine how `random` is declared. I get what you're saying about type inference, but Rust doesn't have function overloading, so we can't define both `fn random() -> u8` and `fn random() -> char`, correct? So, how?
The random() function is a generic function that takes a type parameter that extends a trait. Here's how that works: First, you need a trait. Let's call it "Random". It defines a single function that returns the type that implements it: trait Random { fn random() -> Self; } Then, you imlement that trait in another type. impl Random for SomeType { fn random() -> Self { // Return a random instance of SomeType } } Now, the random() function itself will look a little something like this: fn random() -> T { T::random() } So when you call let foo: SomeType = random(), it will internally call the SomeType::random() function. The actual function in the rand crate is actually quite a bit more complicated. But it boils down to this. I hope I managed to explain it well enough.
@@nellykantu8428 Java's not a terrible option especially if you want to get paid! Take a look at the graph on this page, any language at the top right you'll be able to get paid work in, in my opinion: redmonk.com/sogrady/2022/03/28/language-rankings-1-22/ You can learn multiple language at once, too, don't forget! What's your goals, hobby or work?
BTW, do you take requests? I've started writing some Rust to fiddle with a USB peripheral, and I've made decent progress with the rusb crate. Where things get messy is interpreting the reports sent by the device. The structure of the USB device's reports are known, and can be represented by a C struct, which we can reproduce in Rust via the #[repr(C)] directive. In C, you would simply setup a byte buffer, have libusb read the report into it, then (essentially) type-cast the buffer contents to the report structure. But typecasting of this type is a big no-no in Rust, especially if the buffer is mutable. There's a facility in the standard library called std::mem::transmute, but it's unsafe, and the autodocs say essentially, "No, seriously, if you think you need this, you probably don't." I took a quick look at serde, but that rabbit hole goes very deep. In short, it looks like all the "obvious" methods for reinterpreting the contents of a memory buffer are unsafe. I was wondering if you knew of a trick that didn't violate Rust's safety rules.
Did you just "Dude, Seriously?" me and then ask for help 20 minutes later? Don't worry about it, I forgive you, it's RUclips making us talk like this, it's all too confrontational here isn't it! Come chat to me and the smart folks on the discord - I bet there a few people who could help you out! But be sure to be courteous and recognise that you're asking for help, not demanding it.
@@NoBoilerplate Your videos are some of the clearest explanations of the advantages and subtleties of Rust I've yet found. It _certainly_ was not my intention to come off as confrontational or entitled, and I apologize.
Ah, it's not the greatest example was it? I have a lot to learn about Rust and I will build better examples as I learn too. HOWEVER the answer here is that the naming examples I used. Strings can have any names, using Enums allows compiler-validated options.
Thanks. That’s what I was wondering. It’s a fine example as far as it goes. I can see that Rust enums are an improvement over C enums, but that’s not a high bar. As programmers what we do want to be able to build types that allow objects to reach only valid states. I think the video articulates that goal well. The way to achieve this is some combination of compile time checking and encapsulation. Encapsulation means that the code that needs to be checked to ensure that only valid states can be reached is limited. Obviously strong static type checking helps here. Mutability restrictions also help. In Java, if a field is “final”, I only have to check the code in the constructors to check that its value is legitimate; and if a field is private, I only need to check the code of one class. If a language distinguishes between mutators and accessors, then I don’t need to check the accessors to see that invariants are respected. I don’t know Rust, but maybe it has something to add here. Another thing that helps is if the language gives a way to express invariants in the language itself; Euclid, Turing, and Eiffel all had this combined with runtime checking. Languages like Dafny, with compile-time verification, and languages like Agda, with dependent types (as another commenter mentioned), give you ways to express nontrivial invariants that can be checked at compile time. In a way, this restricts that amount of code that needs to be checked by the developer down to the code that expresses the invariant. It will be interesting to know more about how Rust supports this goal.
7:01 I'd use the filter Function. For example, you can remove all elements of a list that are 3 like this: let v = vec![ 1, 3, 2, 11, u16::MAX ]; let v = v.into_iter() .filter( | item | item != 3 ) .collect(); Great video, and I'll be sure to have a look at that podcast
"Make invalid states unrepresentable" "Don't you see that the whole aim of Newspeak is to narrow the range of thought? In the end we shall make thought-crime literally impossible, because there will be no words in which to express it."
@@NoBoilerplate ha! I was trying to figure out why when I tired it, it didn’t work. This seems like a sensible addition as it does take variable names directly now, why not also structs?
@@RoamingAdhocrat I do try to keep things as readable as I can, and my code is far from perfect! My goal with this video was to talk about structs and enums and emphasise that modelling your correct states is vital when building a Rust app. There are lots of improvements to the code that need doing!
Extremely similar - the original Rust compiler was written in OCaml! I see Rust as Haskell/OCaml dressed in C's clothing (and you can get paid to write it!)
@@NoBoilerplate Totally agree that type system has the potential to make the greatest language ever, please keep the great work! I started learning Rust because of your videos and I'm planning to rewrite my side project using it but still not confident enough 😅
in the beginning when implementing the Name enum 5 #[derive(Debug, RandGen, Display)] ■■ this function takes 2 arguments but 1 argument was supplied supplied 1 argument ▎ 4 enum Name { ▎ 3 Akira, Californa, Daedalus, ▎ 2 Eisenberg, Interpid, Miranda, ▎ 1 Nova, Reliant, Sagan ▎ 24 } I get the Error that RandGen takes 2 arguments but 1 is supplied and I cannot get rid of it can someone help?
i enjoy your other videos, as theyre what motivated me to learn rust to supplement my own diet of python spaghetti, but after a point the insights of a long-time developer would do nothing but go over the head of a beginner like me. Thats why this video made me so excited, before i wasted a lot of time over this confusion: at 1:12 you introduce the rand_derive 2 crate and the randgen trait, but not Display. Is this a mistake? i had to go looking through the docs.rs to find the displaydoc crate, and i still couldnt be sure it was the right crate with a Display trait, since you don't show the use statement that would clarify. even then, the text you show on screen wouldnt compile: the Display trait asks you to write doc comments for every member of the enum...... which is redundant, because the enum's members are names, and names are self-explanatory. You never mention the Display trait by name in the video, and using the displaydoc crate, the code on screen woud not compile, and if it did, then the trait would seemingly help nothing. Is its inclusion in error?
Take a look at the source markdown, on my linked repo, some code I have no time to show in the videos, but the compiler sees it! I should have included a note about Display, however. Thank you!
> I'm not delighted by having to resort to indexing the vector. I would lean towards using a regular for-loop for this, maybe something like: for section in &mut station.sections { if section.name == section_name { section.active = true; // break? } } You could also consider making your sections a HashMap instead of a Vec, with the section name enum as the key to the map. That would let you get rid of the loop here, and it would let you guarantee that you never have multiple sections with the same name. If you wanted to get really crazy, you could even assign values starting with 0 to your SectionName enum, and you could use an array of (optional?) Sections indexed by the value of their SectionName. Ideally you could set the size of the array to the number of enum variants without any duplication, but I'm not sure there's a good way to do that today. Something more verbose like this example using a struct could also work: doc.rust-lang.org/std/ops/trait.Index.html#examples
@@NoBoilerplate The name tag method used "self" and not "&self", so the method took ownership of the dog and freed it. Try it yourself, you cannot access the dog instance after calling the name tag method. It's just a joke, though. :)
0:30: "But how do we design programs in Rust? There's no classes!" Dude, seriously? Some of your audience are veteran users of C and assembly code, and they don't have classes, either. 6:00: _(sigh...)_ I've never understood this pattern -- two different functions that, save for one tiny difference, perform _identical_ operations. This way leads to code bloat. You should parameterize the boolean test, along the lines of (horrible formatting ahead): fn sections_with_active (&self, active: bool) -> Vec { self.sections.iter().filter (|m| m.active == active).map (|m| m.name.to_string()).collect() } Which you can then call as `station.sections_with_active (true)` or `station.sections_with_active (false)`, which is hopefully sufficiently mnemonic.
I don't really like that as a public API. It doesn't read as nice. You could absolutely have working_sections and broken_sections call this new sections_with_active, though - I do prefer that.
NASA should use rust as their official programming language... (after watching this I hope so) its always better to evolve with time RUST is evolution... >Rust for next 40 years {spread the word}
Rust's not yet PROVEN (mathematically) for safety critical systems, when people's lives are on the line, you gotta be SURE. It may be, but it's not there yet. Doesn't matter for 99.99% of applications, of course. (how else do you explain php?!)
@@NoBoilerplate are there any languages that are proven mathematically? surely C can't be with all the many many many ways there are to trigger undefined behavior
@@lucky-segfault The ones used in autopilots and pacemakers are, en.wikipedia.org/wiki/Ada_(programming_language) and co. They either use or have core features proven by Formal Methods such as Z, B, Coq and so on. It's not really a deficiency in Rust, as to formally prove programs, their state has to be SO TINY (think pacemaker inputs and outputs) and a single unbounded string can make your function impossible to prove.
@@NoBoilerplate oh neat. Well, it sounds like rusts ability to create programs that can't represent invalid states might be a useful base for proving individual programs are mathematically provable, even if the language itself still has openings for errors
@@lucky-segfault Certainly. Just like seatbelts, a policy doesn't have to stop every fatality for it to be wildly successful. Rust feels like 99% of my day-to-day errors in python just can't happen. Wonderful!
I don't mind at all, I totally understand. However, I'm hoping to do these videos professionally, and the way to do that is through advertising. If I can get sponsorship from relevant companies (not squarespace lol) then I'll be able to dedicate more time to making these videos for you. That sounds like a win/win for everyone!
@@NoBoilerplate I'm of the opinion that people RUclips-ing fulltime ruined the feeling of the platform. It is very corporate and business now. It is hard to find users that make videos in their free time for fun, which was very easy years ago when RUclips was still a smaller platform where nearly nobody did it full time.
@@ironnoriboi I don't think this is the right way to look at it. Let me know your thoughts on this, which says it much better than I could: ruclips.net/video/sUsI6W7-d28/видео.html
@NoBoilerplate, here's what ChatGPT helped me make. Is there no crate already out there that does this? use std::io::{stdout, stdin, Write}; use std::str::FromStr; pub fn prompt(message: &str, validate: F) -> T where T: FromStr, T::Err: std::fmt::Debug, F: Fn(&str) -> Result { loop { print!("{}", message); stdout().flush() .expect("Failed to flush stdout"); let mut input = String::new(); stdin().read_line(&mut input) .expect("Failed to read input"); let input = input.trim(); match validate(input) { Ok(value) => return value, Err(error) => println!("{}", error), } } } pub fn prompt_value(message: &str) -> T where T: FromStr, T::Err: std::fmt::Debug { prompt(message, |input| { match input.parse() { Ok(value) => Ok(value), Err(_) => Err("Error parsing input"), } }) }
ChatGPT's fun isn't it! I also am using as part of my daily work. HOWEVER this seems wildly complex. Do you really need all that generic stuff? I see you're more interested in the input side than the validate side (your code expects a validate function to be passed in) There's lots of input crates available crates.io/search?q=input What are you attempting to do?
@@NoBoilerplate With the FromStr (trait?) it allows for identifying a parsable type. That way you can simply do this: mod prompt; use prompt::prompt_value; fn main() { let age = prompt_value::("Enter your age: "); println!("Your age is: {}", age); } It's nice because prompt_value won't return until it has a valid value. Command line apps are my way of experimenting.
ERRATA
- 0:43 This is not how format strings look - sorry! It compiles but doesn't do what I wanted.
- 1:07 misspelled "California"
- 2:15 duplicate variable name "rand_int", it should of course be "rand_float"
- Because randomising sections can cause duplicates, your searching will find the FIRST instance of the section, not necessarily the one you want. The fix for this is left as an exercise for the viewer, and future me, apologies!
- 7:00 thanks to everyone who said that `iter.find()` would be perfect for this job!
- Thread panic on station breakdown reported by @Mitch557. Logic bug in my game loop, but unrelated to the type system demo github.com/0atman/noboilerplate/issues/9
dont forget to pin this comment ;)
@@KyraKrassenburg omgggggg youtube LOVES unpinning it when I edit - THANK YOU!
@@NoBoilerplate haha i didnt realise thats what happened.. thats odd lol
0:42 formatting uses single brackets, you can't put arbitrary expressions inside the brackets, and the method should take &self, not self.
It was removed from pin once again.
Hey look ma, No Boilerplate uploaded again!
I'm as excited as you are!
😂😂 hi ma I'm a underrated semi-famous coder 😂😂❤❤
A favorite pattern of mine is the free generic. Say you have several objects with ids that are all the same underlying type, such as Uuid. You can have a type, say TypedId(pub Uuid, PhantomData). This is generic over your structs and only holds PhantomData for the free generic. It is no larger than a Uuid, but now, you can't mistake a Station id for a Section id!!! This is better than `StationId(pub Uuid)` because you only have to implement things once while still getting type safety!!
now that is COOL!
Could you post a small working example?
@@ArrekinWorks Absolutely! In the example, I've also included a link to a project where I'm using this, in case you want a "real world" example.
Note: RUclips doesn't like me posting links, so go to the Rust playground and add gist=34f765f98445ca4c2e8da6fbab6a53f8 to the URL
@@tylerbloom4830 Amazing, Thank you! I'm still early in my days of learning Rust but I already hit this issue with having one type as id for different structs that should not be treated as one type and so far I used structs `Alias(pub IdType)`. I didn't know about PhantomData, so thank you for the larger snippet :)
@@ArrekinWorks No problem! I was doing the same thing for a while too! What I really wanted was a transparent wrapper. Uuid implements lots of neat stuff, and I'd prefer not to write the boilerplate for it all. But Defer will have to suffice.
From "you can use rust to make your life simpler" to "LETS BUILD A SPACE STATION"
I like where this is going!
TO THE MOON!
@@NoBoilerplate come to think of it, rust would be perfect for infrastructure on the moon!
@@w1keee And the Mars
the unrepresantability of invalid states is one of my favourite rust features. i hope the devs add ranged integers soon!
There's already crates that do this, get to it!
Exactly what I was thinking!
@@NoBoilerplate It does kind of feel like the sort of thing that wouldn't be out of place in std, especially since the compiler could definitely use them as an optimization hint.
Im running into an issue where i watch one of your videos to learn about rust and then i wake up 2 hours later because your videos and voice are so peaceful that i drifted off into an afternoon nap. Thanks for making great content.
I'll take that as a complement! If you want actual relaxing content in my voice, I'd love to know what you think of my sifi, hopepunk podcast, Lost Terminal ruclips.net/video/p3bDE9kszMc/видео.html
@@NoBoilerplate I use it for this precise purpose.
@@erikyoung5139 thank you!
I don't have a use case that really requires me to use Rust, but hell, you've gone and made it impossible to resist. I just dropped everything I was doing to watch this and was not disappointed. Bravo!
Things you can do in Rust:
Frontend web dev with yew.rs
backend web dev with rocket.rs
game dev with bevy
Bare-metal coding with many no-std crates!
@@NoBoilerplate Desktop apps with Tauri, crypto with Solana..
@@lardosian Tauri isn't really Rust, I'd spend my days writing my javascript app INSIDE Tauri, right?
@@NoBoilerplate Have not actually used Tauri, just heard about it recently.
@@lardosian Yeah, I got quite excited about it as they went 1.0 recently right? However if you look into it, it's an electron clone. A REALLY GOOD ONE, faster and more secure, but you still write your app in web technologies. Not a bad thing, per se, but I'm hardly looking forward to maintaining another js project...!
I felt much in the same way as you do when I was building a personal tool.
I found the `From` trait was extremely helpful in defining how to move from one Type to another, because once I'd done it for all possible types I was using, most of my code was just calling `.into()` and it ended up looking like a flow of data transforming from one state into another.
A nice side-effect is that almost all of you're logic that handles transforms between Types are easily located in single places (where ever you implemented the `From` trait) so it's all easy to find.
That's a great tip, thank you!
7:04 I'd do something like this:
if let Some(sec) = station.sections.iter_mut().first(|m| m.name == section) {
sec.active = true;
}
If you'd like to panic on not finding it, just else-block panic or use match instead.
Alternatively, you could just use .for_each(), as Options are iterable themselves. That way you could also easily change from .first() to .filter() if behavior is desired where multiple matches were possible.
Thank you!
To panic after not finding the section you could also do this:
let broken_section = station.sections.iter_mut().find(...).expect("Section not found.");
broken_section.active = true;
Or alternatively this, if you like doing things in a single statement:
station.sections.iter_mut().find(...).map(|sec| { sec.active = true; }).expect("Section not found.");
But I'd personally prefer the first one.
@@Betacak3 Nice, that's much better than indexing!
I just came across this channel and, wow, it has completely revitalised my drive to make stuff in Rust.
Mission accomplished. The language is incredible, as I try to show in my videos. start here fasterthanli.me/articles/a-half-hour-to-learn-rust then hit the book, and come talk to the community on the discord server (links on noboilerplate.org)
I was watching your podcast even before this video came out!
A Day #1 fan! Thank you so much, I love writing Rust videos, but Lost Terminal is the work I'm most proud of :-)
This video just made me so unexplainably happy ^-^ Both because of the amazing technical aspects and the beauty of rust you showcased, but also because of being reminded of Sev through the topic and your voice after having absolutely fallen in LOVE with Lost Terminal the past few days
I'm so pleased! Thank you! LT is my favourite thing in the WORLD!
This is an amazing guide on how to structure software in general. I have trouble telling my friends and explaining to them how to nicely structure software, and this is an excellent guide. Please make more guides like this because even if only for rust they make for stunning teaching tools.
Well thank you so much! I plan to write a video on Rust modules in a similar way soon!
Love your content!
Turn audio compression down, the unnatural sound is diatracting. Device speakers will compress your voice. Users using earphones won't need the compression.
Also pushing less air into consonants and more into vowels will do your delivery wonders. You've got a great voice for what you love doing.
Keep it up and thank you!
Thanks for the tips Wayne, always appreciated. Could you give me specific timecodes for the problems you're mentioning? Thank you!
@@NoBoilerplate I'm not sure what he's referring to. The only thing I noticed was a couple mouth sounds, which is fixed by moving farther from the microphone. Wasn't really a bother, though. Great video! I'm excited to see more specific examples of ways rust has empowered you to write software in a more provable and less guess-and-check way.
@@NoBoilerplate The voice and cadence is literally a + in my opinion.
@@dardevelin Thank you so much! I just wrapped season 9 of Lost Terminal - I've had a LOT of practice! ruclips.net/video/p3bDE9kszMc/видео.html
Note that while Java's type inference isn't as good as Rust's, it can perform type inference based on both sides of the assignment expression. To infer from the left side use the diamond operator:
ArrayList foo = new ArrayList();
which is equivalent Rusts's
let foo: Vec = Vec::::new();
and to infer from the right side use the var keyword:
var foo = new Arraylist();
which is equivalent to Rust's
let foo = Vec::::new();
Anyway, nice job on the videos, they're pretty good.
Thank you so much, and thank you for telling me about Java's ability. Using let/var isn't QUITE as nice as Rust though! ;-)
The type state pattern is also incredibly useful here. Things like keeping track of whether a file descriptor is open or closed in the type, or making a builder that requires certain values to be set, is very useful.
Admittedly, code that makes heavy use of this looks terrible, though. (For the implementation - the API that results from it is very neat :D)
thank you so much for telling me this, I now have a name for the pattern!
@@NoBoilerplate Tyestate is the part of Hermes that Rust took to make the borrow checker. When the full thing is built directly into the language, it becomes much easier to read everything. Hermes somewhat over-used it, but it would be nice if more languages incorporated it directly.
Amazing Video !, Thank you very much for giving away this presentation. As a Rust newbie (Coming from high level languages) this kind of explanations are very useful to change the traditional mindset and think in a "Rusty" way. Looking forward for more Videos !. thanks again
Super! I've also come from high-level languages. I have made a few of these, check out the playlist here and let me know what you think! ruclips.net/video/Q3AhzHq8ogs/видео.html
Just discover this channel yesterday. This is really nice content, your voice is pleasing to listen to!
Oh have I got good news for you, there's 9 seasons of me narrating Lost Terminal so far, I'd love to know what you think! ruclips.net/video/p3bDE9kszMc/видео.html
New rust video from tris.. instant like
You're too kind!
For enums you could use the discriminant function if you want to know if a enum variant is the right variant you are looking for without describing them. You can also use type_name_of_val function if you want the name of the type but don't know the type name. It is in experimental though.
oh cool, I'll look that up, thank you!
Welcome.
Tris you may enjoy a book called "Forever War" by Joel Haldeman. Thanks for inspiring me to keep going deeper with Rust, the struggle never stops ❤️
You're right, it's on my bookshelf right now, loaned to me by a friend and ready to read. I will try to do so.
I have a recommendation for you: The Spin Trilogy. Incredible stuff.
I love the crate suggestions! There are so many great crates out there I didn't even know I wanted
I've not even SCRATCHED the surface! look at THIS crates.io/crates/no-panic
Wake up babe.
New No Boilerplate video just dropped.
I'm as excited as you are!
I had a VERY similar idea for a game exactly like the one you talk about! Can't wait to give it a try!
Try out crates.io/crates/bracket-terminal for your simple game engine!
Love Cool Retro Term. I've also used it for videos myself, love to see it used here also!
Thank you so much! Did you see the podcast videos? ruclips.net/video/p3bDE9kszMc/видео.html
If he thinks this gives him super powers, wait till he finds out about Bevy's ECS, and how Structs can be used as components.
(That is assuming he doesn't know it already, or that he's patient enough to sit trough it's compile times, I have a suspicion that he is.)
(I'm also thinking of trying out macroquad, or the rust bindings for SDL. I was even considering building a ray casting engine in Rust, I mean sure a lot of my code will be from some tutorial, that I'd try to translate to Rust, but it might be fun...)
LOVE Bevy!
Oh my god, this video is great!! I love you, this will save me a lot of trouble. Please make more videos on how to best write Rust Code. And your voice is one of the most charming ones to listen to, reminds me a lot of Sebastian Lague.
Thank you so much! If you'd like to listen more, you might like my scifi podcast Lost Terminal ruclips.net/video/p3bDE9kszMc/видео.html
@@NoBoilerplate Thanks for the hint! I'll listen to everything :)
@@SEOTADEO Do comment as you go, I'd love to know your thoughts!
While the possibility to use things in a place before you define them certainly feels convenient, as a reader of other people's code, I have learned to appreciate C and its requirement of linearity. I don't have to constantly jump around the code and nothing can ever surprise me because I know that if it's being used, I must have already seen it by the time it is used.
Though I understand where this is coming from, it's only true in trivial programs. Once you start splitting up things into files, you lose this linearity of behaviour.
However, on the whole, I agree that top-to-bottom reading makes absolute sense - the thing is that sometimes I just want to prototype something close to where I'm working, and then refactor it to the 'correct' position later.
With Rust having so many powerful functional language features like immutability-by-default, first class functions, pattern matching, closures, iterators, and so on, surely it seems the safest Rust paradigm is to have no state at all?
Love writing code like that, but now and then it's nice to have just a LITTLE state for some problems. I'm glad Rust is pragmatic so I can get work done - the real world is full of state!
@@NoBoilerplate I guess that is true. I also just learned that Rust does not (I stand to be corrected on this) have good native support for monads, which makes the whole stateless paradigm a lot more frustrating.
@@doormango I'll have to trust you on that.
Simply amazing!
Thank you so much!
These would have been SO HELPFUL for me to learn Rust! Also, I like the little text adventure like style here, because I've been writing a text adventure in Rust (with Macroquad for rendering) for over a year now
oh sweet!
3:06 after functional experience I hate using something before defining. All of my programs you should be able to start on line 1 of the first file and read to the last line of the last file (most languages don't have an order of files but I make a note, so it's easier for me to come back to the project) and you will never come across a reference to something you have not seen the definition of.
For instance let's say you have never seen the code before and you get to the struct and you see SectionName, you haven't seen that before so you then need to find where the definition is and read it before going backwards back to where you were, and you have to do this for every bit of data that references after the current line which can get annoying having to jump around constantly and it requires more work to understand something.
I've had someone say to me that they like to have the main thing first because that's what someone reading will see and they will see the context of what some data is before seeing the definition - but to that I say that you have file names to tell you what is contained and then you have sensibly named data so that when it is all combined you should already have a good idea of how it is structured. The tradeoff is that you will be told about things that aren't immediately used that you will need to keep tabs on but that you never jump around.
Also, my thoughts do start in the main body of the program and then they branch upwards until I reach an end and then I write it. I'm used to F# where the first time I run the program it may already be completely finished.
Rust's the same, if it compiles it works! F# and Rust both take after Haskell in that regard.
I don't think your 'using something before defining' problems scale well. As soon as you split up your app into multiple files (isn't that most non-trivial programs?) you're going to be jumping to definition anyway? It's a good rule per-file, but imports exist...
Do you need to jump to StationName? It's the name of the station, clearly. If you aren't looking specifically into how names work in the codebase, you probably don't care about the actual implementation. Getting a high-level overview is usually more useful, so I am also of the opinion that the most important bits should come first.
Define things top-down, if you will.
If you're following the philosophy of making things easier to read because they're going to be read more than written, it makes sense to order things a little differently, namely putting interfaces and public things near the top, and implementation details further down. Sometimes this lines up, but not always.
It feels "neater" to order things by how to compiler will need it, but humans are good at digging into things only when they need to.
Amazing content. Glad you exist.
What a nice thing to say! You're very kind, thank you!
This was a very entertaining video 🙂 Thoroughly enjoyed
Thank you so much! There's 12 others in my rust series, I'd love to know what you think about the others!
7:00 I think you could use
*station.section.iter_mut()
.find(...)
.expect(...)
.active = true;
or something like that.
.find()! nice!
@@NoBoilerplate Yep, if you look at the documentation e.g. for std:iter::Iterator, or one of the std::collections, you'll find these APIs where you thought you had to do it with two or three methods, or manually handling an index, but there's a method that lets you do it in one with no index required. Much of the time, clippy will pick these up for you too.
@@Tigregalis no clippy help this time for me, I always run it! thank you!
Why do you .expect on section repair when it is easy to assume that section could not be found? Is it really unexpected? Should we panic because of that? I'm asking out of curiosity, is it good approach or should we rather return some kind of error? (Rust newbie here)
Yes indeed, in a perfect world you don't have any paths that panic at runtime! Check my "rust on rails" video for a better demo of this feature
Instant subscribe. Great video. Informative, interesting and moderately brief
Thank you so much! Have you seen my other Rust videos, they're all packed with really cool Rust features, and moderately brief too :-D
@@NoBoilerplate I'll be doing a deep dive before long. I smashed the bell for updates.
Oh, Lost Terminal was really nice. It was clever and humorous and well written, and the music was great. I like how you've paired your passions.
@@bmanturner Thank you so much! 9 seasons so far!
Thanks for another great video!
My pleasure!
0:43 I was wondering if Rust supports compile-time range constraints. What if we want Dogs to only have a maximum age of 30? A dog with age 128 is obviously not a Dog.
What if I want the compiler the throw a compile error when a string doesn't match a constant regex? What if I want the compiler to show me a warning when it can't verify that an arbitrary String matches the regex?
Raising exceptions when a value is invalid (while its type is correct) is usually done at runtime in most langs. I want to do that at compile-time
couldnt this be done manually by writing another line of code with the assert macros?
@@PokeNebula Assertion happens at runtime iirc, but you're on the right track, this could probably be implemented with macros
Rust does not support arbitrary constraints about "this type but only these values" in its type system, or at least not directly in the form where the original type is clearly still there. However, const generics are very powerful! And so are macros! There is a crate for your use case with bounded integers, fittingly called bounded_integer. It has a BoundedU32 (and friends matching all of the other integer primitives including BoundedIsize) which takes a minimum and maximum value generically. For example, BoundedU8 if you want dogs to be only between 0 and 30 years old.
There are some limitations to this approach though, and they don't implement Default among other things. The same crate also has a macro bounded_integer!() which allows you to create a new type which behaves pretty much the same, but macros are slightly more powerful and the type you get out of that will implement some more traits. You could, for example, create the dog age like so: bounded_integer! { pub struct DogAge { 0..=30 } }. I haven't actually used this crate so i don't know why it provides this "struct" form, because the other form of bounded_integer ! { pub enum DogAge { 0..=30 } } should behave the same, but the the enum form makes invalid states unrepresentable by its internal representation, so the compiler can use one of those invalid, out-of-range values to fit other things, so if you might not know the age of a dog, an Option will still be the same size.
Also note the existence of std::num::NonZeroU8 and friends, which is basically a BoundedU8. The non-zero requirement is the most common range requirement as zero is a funky value for certain operations, and it can't be the denominator in a rational number for example. I think its actual use case and reason for being in stdlib is pointers though, and using similar tricks the compiler knows that a Box can never be null, and represents the None case in an Option as the null pointer. I'm getting carried away though, as this is for some specific optimization reasons and not for the semantic reason you wanted that "well, dogs don't live to hundreds of years", so let's go to the next thing.
String regexes? Well, there's probably a crate out there for that. Several crates do basically the same thing (though not necessarily based on regexes) such as sqlx with compile-checked queries, and one that i've personally used is hex-literal, which i've used to convert color codes "#FF0000" into an Rgb([u8; 3]) for image processing. And if you've ever written any Rust, i can guarantee you've used println!(). The format string has to match a specific format described in std::fmt module-level documentation.
The examples i've mentioned for strings all operate on string literals though, but you said "when it can't verify that an arbitrary String matches the regex". Well, no. The compiler doesn't do that. Not for the String type. But what you could do is create a newtype wrapping a String (perhaps even a Cow
@@sodiboo lmao. Don't worry I like learning (and I sometimes also write/talk too much without noticing), I read the whole thing! I already knew about Path strings and OsString, but everything else was very helpful and interesting. Thank you. You should make a video about it, to spread information for other beginners
For your dog example the age would have to be known at compile time and not change at runtime. If that's the case, it could be a const generic and in that case there are hacky ways you can use to make compilation fail if it's not in a certain range, not a power of 2, or something like that.
While I think we all appreciate the hustle of trying to get more views for your projects the length of the pitch for the podcasts is beginning to distract from the content of these videos which are where you are excelling and finding an audience.
I understand, it won't be an often repeated thing. However your options are for me to tell you about my own projects for 30s or to advertise NordVPN for 30s. I am not very interested in nordvpn.
@@NoBoilerplate Honestly, I think it was fine. I sponsor block, but have it set to not automatically skip self-promotions. It gave me the option, but I decided not to skip it.
It's not like some other videos where they have 3 minute sponsor segments in their 5 minute videos... Plus, it was at least tangentially relevant to the video, so it wasn't as jarring as these things usually are.
@@NoBoilerplate yeah that’s fair, the deeper integration into the video in this case threw me off because it wasn’t clear how much of the topic was guided by what you think is most useful to know and what was relevant to podcasts
@@notusingmyrealnamegoogle6232 Thank you for your understanding. I will very likely do a few external sponsors now and then, but not freakin' squarespace, I'd rather they be Rust specific companies or tech or something relevant to my audience.
The bottom line is that if I can make some money from these videos I can do fewer hours at my day job and make more and better videos for the channel!
I noticed you have a lot of unwrap calls. Is it safe to use if you are certain what you are unwrapping will not fail?
You're quite right, every unwrap call you make is a chance to improve the reliability of your program.
However, when you are prototyping, like I am here, you can leave a few in, consider them todos that the compiler understands.
I use clippy to warn on them, and turn them into errors in my build pipeline!
@@NoBoilerplate I see. Theres a lot to learn here :)
Nice work, beginning to look forward to these now :) Small feedback though - could you look into putting a pop filter or something similar over your mic, the `s` sounds currently are too harsh on the ears.
interesting, I'll look into it.
(A pop shield won't do anything for sibilants, that's for plosives, but your feedback stands, thank you)
@@NoBoilerplate Gotcha. I learn something new everyday :)
@@SriKadimisetty I've got a lifetime of music production under my belt, and 2 years of intensive podcasting for Lost Terminal, so I'm always optimising my audio pipeline!
btw have you heard it? ruclips.net/video/p3bDE9kszMc/видео.html
2:15 I'm sorry but I fail to imagine how `random` is declared. I get what you're saying about type inference, but Rust doesn't have function overloading, so we can't define both `fn random() -> u8` and `fn random() -> char`, correct? So, how?
random() -> T
The random() function is a generic function that takes a type parameter that extends a trait. Here's how that works:
First, you need a trait. Let's call it "Random". It defines a single function that returns the type that implements it:
trait Random {
fn random() -> Self;
}
Then, you imlement that trait in another type.
impl Random for SomeType {
fn random() -> Self {
// Return a random instance of SomeType
}
}
Now, the random() function itself will look a little something like this:
fn random() -> T {
T::random()
}
So when you call let foo: SomeType = random(), it will internally call the SomeType::random() function.
The actual function in the rand crate is actually quite a bit more complicated. But it boils down to this. I hope I managed to explain it well enough.
Thank you for explaining it, that's very clear!
@@Betacak3 Yes! Rust traits are so versatile, they can even be used like "overloading done right".
@@Betacak3 I forgot to thank you for this!
Fun and informative. Thanks!
Thank you so much! Did you see my other videos in this Rust series?
@@NoBoilerplate Yup, looks like almost all of them. I'll probably be checking out the couple I missed this afternoon.
@@AceofSpades5757 How super, I'd love to hear what you think of them, if you fancy commenting on them.
You're amazing fr, wish i could code like u
You're too kind: My code is not yet very good, I'm still learning Rust every day! What's your coding experience?
@@NoBoilerplate I'm still beginner, was learning Java but dropped it to learn rust
@@nellykantu8428 Java's not a terrible option especially if you want to get paid!
Take a look at the graph on this page, any language at the top right you'll be able to get paid work in, in my opinion: redmonk.com/sogrady/2022/03/28/language-rankings-1-22/
You can learn multiple language at once, too, don't forget! What's your goals, hobby or work?
You absolute legend
thank you!
BTW, do you take requests?
I've started writing some Rust to fiddle with a USB peripheral, and I've made decent progress with the rusb crate. Where things get messy is interpreting the reports sent by the device.
The structure of the USB device's reports are known, and can be represented by a C struct, which we can reproduce in Rust via the #[repr(C)] directive. In C, you would simply setup a byte buffer, have libusb read the report into it, then (essentially) type-cast the buffer contents to the report structure. But typecasting of this type is a big no-no in Rust, especially if the buffer is mutable.
There's a facility in the standard library called std::mem::transmute, but it's unsafe, and the autodocs say essentially, "No, seriously, if you think you need this, you probably don't." I took a quick look at serde, but that rabbit hole goes very deep. In short, it looks like all the "obvious" methods for reinterpreting the contents of a memory buffer are unsafe. I was wondering if you knew of a trick that didn't violate Rust's safety rules.
Did you just "Dude, Seriously?" me and then ask for help 20 minutes later?
Don't worry about it, I forgive you, it's RUclips making us talk like this, it's all too confrontational here isn't it!
Come chat to me and the smart folks on the discord - I bet there a few people who could help you out! But be sure to be courteous and recognise that you're asking for help, not demanding it.
@@NoBoilerplate Your videos are some of the clearest explanations of the advantages and subtleties of Rust I've yet found. It _certainly_ was not my intention to come off as confrontational or entitled, and I apologize.
@@ewhac Apology absolutely accepted, don't worry about it 🙂
Nice video. But here’s what I’m missing: What are the states in the example that are invalid and why are they unrepresentable?
Ah, it's not the greatest example was it? I have a lot to learn about Rust and I will build better examples as I learn too.
HOWEVER the answer here is that the naming examples I used. Strings can have any names, using Enums allows compiler-validated options.
Thanks. That’s what I was wondering. It’s a fine example as far as it goes. I can see that Rust enums are an improvement over C enums, but that’s not a high bar. As programmers what we do want to be able to build types that allow objects to reach only valid states. I think the video articulates that goal well. The way to achieve this is some combination of compile time checking and encapsulation. Encapsulation means that the code that needs to be checked to ensure that only valid states can be reached is limited. Obviously strong static type checking helps here. Mutability restrictions also help. In Java, if a field is “final”, I only have to check the code in the constructors to check that its value is legitimate; and if a field is private, I only need to check the code of one class. If a language distinguishes between mutators and accessors, then I don’t need to check the accessors to see that invariants are respected. I don’t know Rust, but maybe it has something to add here. Another thing that helps is if the language gives a way to express invariants in the language itself; Euclid, Turing, and Eiffel all had this combined with runtime checking. Languages like Dafny, with compile-time verification, and languages like Agda, with dependent types (as another commenter mentioned), give you ways to express nontrivial invariants that can be checked at compile time. In a way, this restricts that amount of code that needs to be checked by the developer down to the code that expresses the invariant. It will be interesting to know more about how Rust supports this goal.
@@teeesen Absolutely, this is a really good idea for a future video - thank you!
7:01 I'd use the filter Function. For example, you can remove all elements of a list that are 3 like this:
let v = vec![ 1, 3, 2, 11, u16::MAX ];
let v = v.into_iter()
.filter( | item | item != 3 )
.collect();
Great video, and I'll be sure to have a look at that podcast
nice thank you! I do hope you like the podcast, it's something I'm so proud of!
You do great work bro. Keep it up bro.❤❤
Thank you so much 😀
"Make invalid states unrepresentable" "Don't you see that the whole aim of Newspeak is to narrow the range of thought? In the end we shall make thought-crime literally impossible, because there will be no words in which to express it."
You've got the principle exactly, nice analogy! I might steal that (with attribution) for a video...! :-D
@@NoBoilerplate you should credit George Orwell
@@sharperguy oh yes of course, but I mean the link between limiting invalid states and newspeak - that's great! 🙂
Have you given thought to beginning to try to write your own AI/NN/etc using rust?
It's not in my area of interest, but there's nearly 1000 ML crates already published! lib.rs/science/ml
Lovely Name enum :D
Hi! What is that `literate` program used to extract the source code in rust, and where can I find it?
well spotted! It's a crate, just install it with `cargo install literate`!
I'll update the readme
Anyone else read the Rust book in Tris's voice now?
I sure do
Huh, I didn't know that format!("{{self.name}}{{self.age}}") was valid syntax. I gotta go rewrite some of my code.
It's not - sorry! Typo there. It COMPILES but is not valid!
@@NoBoilerplate ha! I was trying to figure out why when I tired it, it didn’t work. This seems like a sensible addition as it does take variable names directly now, why not also structs?
@@Dygear I had a nightmare I wars writing python and it came true 😂
Awesome possum ⚡️
Thanks 🤗
Nyc video, something unique thanks man❤️🙂
My pleasure 😊
2:30 can you not create a shuffed vector in one step, by chaining `shuffle` off `collect`?
Yeah! Well spotted, I love chaining method calls like this!
@@NoBoilerplate Then it wouldn't need to be mutable, right? I thought perhaps you'd done it on two lines for readability
@@RoamingAdhocrat I do try to keep things as readable as I can, and my code is far from perfect! My goal with this video was to talk about structs and enums and emphasise that modelling your correct states is vital when building a Rust app.
There are lots of improvements to the code that need doing!
How does Rust type system compares to OCaml's?
Extremely similar - the original Rust compiler was written in OCaml!
I see Rust as Haskell/OCaml dressed in C's clothing (and you can get paid to write it!)
this is amazing!
Thank you! I'm not even that good at Rust yet, but the type system has such POTENTIAL! I'll dig deeper in future videos as I figure it out myself!
@@NoBoilerplate Totally agree that type system has the potential to make the greatest language ever, please keep the great work! I started learning Rust because of your videos and I'm planning to rewrite my side project using it but still not confident enough 😅
@@hussein7859 I'm so pleased! Do come and chat on my discord, ask any questions in #newbie-advice if you need!
@@NoBoilerplate I will, Thanks!
I like it
I love it! horse.gif
Have you seen my other videos? don't watch my last one, I made big mistakes about Go lol!
What do you use to create your slideshows?
Obsidian.md. I talked about it in the middle of this video, it's GREAT! ruclips.net/video/ifaLk5v3W90/видео.html
Thanks! Great videos.
@@CT-cx8yi Thank you so much!
for 7:06, station.sections.iter_mut().find(|m| m.name= = section).expect("...").active = true should work
.find() - nice!
Mr. Plate,
Could you make a video on WebAssembly (please 🥺🥺🥺)?
kthanksbye
I may do in the future, but for now just use yew.rs it's great!
And PLEASE! Mr Plate is my father, call me Nob Oiler.
"Mr. Plate"
lmao
Rust
Rust
7:01 what about
if let Some(ref mut section) = station.sections.iter_mut().find(|m| m.name == section) {
section.active = true;
}
Nice, yeah great optimisation!
What if we played Among Us on the No Boilerplate space station? 😳
ha!
And of course, I’m going to take this and blow the scope like a hot air balloon.
BUILD IT!
0:36 why would anyone want to program like in OOP? Also Rust does have typeclasses, they're just called traits instead ;)
Ah yes!
in the beginning when implementing the Name enum
5 #[derive(Debug, RandGen, Display)] ■■ this function takes 2 arguments but 1 argument was supplied supplied 1 argument
▎ 4 enum Name {
▎ 3 Akira, Californa, Daedalus,
▎ 2 Eisenberg, Interpid, Miranda,
▎ 1 Nova, Reliant, Sagan
▎ 24 }
I get the Error that RandGen takes 2 arguments but 1 is supplied and I cannot get rid of it
can someone help?
edit: solved. I used the wrong version of rand 0.7 instead of 0.8
Super! This is somewhat my fault as I don't check in my cargo.toml. I will fix this for the next video!
can't put boilers into space.
they're too heavy
Does it even support Multiple Dispatch? Everytime I look at Rust, it just looks ugly. Why not something nice like TypeScript, with Rust compiler?
Can you explain why I would need multiple dispatch? I've not used a language that has it.
am
am i the only one who thought he was gonna make a space station in Rust
THE SURVIVAL GAME RUST?
ikr
i enjoy your other videos, as theyre what motivated me to learn rust to supplement my own diet of python spaghetti, but after a point the insights of a long-time developer would do nothing but go over the head of a beginner like me. Thats why this video made me so excited, before i wasted a lot of time over this confusion:
at 1:12 you introduce the rand_derive 2 crate and the randgen trait, but not Display. Is this a mistake? i had to go looking through the docs.rs to find the displaydoc crate, and i still couldnt be sure it was the right crate with a Display trait, since you don't show the use statement that would clarify. even then, the text you show on screen wouldnt compile: the Display trait asks you to write doc comments for every member of the enum...... which is redundant, because the enum's members are names, and names are self-explanatory.
You never mention the Display trait by name in the video, and using the displaydoc crate, the code on screen woud not compile, and if it did, then the trait would seemingly help nothing. Is its inclusion in error?
Take a look at the source markdown, on my linked repo, some code I have no time to show in the videos, but the compiler sees it! I should have included a note about Display, however. Thank you!
What is that noise?
You'll have to be more specific!
🦀
Ferris!
> I'm not delighted by having to resort to indexing the vector.
I would lean towards using a regular for-loop for this, maybe something like:
for section in &mut station.sections {
if section.name == section_name {
section.active = true;
// break?
}
}
You could also consider making your sections a HashMap instead of a Vec, with the section name enum as the key to the map. That would let you get rid of the loop here, and it would let you guarantee that you never have multiple sections with the same name. If you wanted to get really crazy, you could even assign values starting with 0 to your SectionName enum, and you could use an array of (optional?) Sections indexed by the value of their SectionName. Ideally you could set the size of the array to the number of enum variants without any duplication, but I'm not sure there's a good way to do that today. Something more verbose like this example using a struct could also work: doc.rust-lang.org/std/ops/trait.Index.html#examples
Nice! I've also had suggestions of using inter.find().
F in the chat for all the dogs killed by people reading their name tags.
Dead, but not forgotten 🕊️🕊️🕊️
I don't understand?
@@NoBoilerplate The name tag method used "self" and not "&self", so the method took ownership of the dog and freed it.
Try it yourself, you cannot access the dog instance after calling the name tag method.
It's just a joke, though. :)
@@Mankepanke oh so it did! Ha!
0:30: "But how do we design programs in Rust? There's no classes!"
Dude, seriously? Some of your audience are veteran users of C and assembly code, and they don't have classes, either.
6:00: _(sigh...)_ I've never understood this pattern -- two different functions that, save for one tiny difference, perform _identical_ operations. This way leads to code bloat. You should parameterize the boolean test, along the lines of (horrible formatting ahead):
fn sections_with_active (&self, active: bool) -> Vec { self.sections.iter().filter (|m| m.active == active).map (|m| m.name.to_string()).collect() }
Which you can then call as `station.sections_with_active (true)` or `station.sections_with_active (false)`, which is hopefully sufficiently mnemonic.
noted.
Boolean parameters considered harmful.
@@31redorange08 That's mainly for flags, that change the control flow of the whole method. This use looks very nice :)
I don't really like that as a public API. It doesn't read as nice.
You could absolutely have working_sections and broken_sections call this new sections_with_active, though - I do prefer that.
NASA should use rust as their official programming language... (after watching this I hope so)
its always better to evolve with time
RUST is evolution...
>Rust for next 40 years {spread the word}
Rust's not yet PROVEN (mathematically) for safety critical systems, when people's lives are on the line, you gotta be SURE.
It may be, but it's not there yet.
Doesn't matter for 99.99% of applications, of course. (how else do you explain php?!)
@@NoBoilerplate are there any languages that are proven mathematically? surely C can't be with all the many many many ways there are to trigger undefined behavior
@@lucky-segfault The ones used in autopilots and pacemakers are, en.wikipedia.org/wiki/Ada_(programming_language) and co.
They either use or have core features proven by Formal Methods such as Z, B, Coq and so on.
It's not really a deficiency in Rust, as to formally prove programs, their state has to be SO TINY (think pacemaker inputs and outputs) and a single unbounded string can make your function impossible to prove.
@@NoBoilerplate oh neat. Well, it sounds like rusts ability to create programs that can't represent invalid states might be a useful base for proving individual programs are mathematically provable, even if the language itself still has openings for errors
@@lucky-segfault Certainly. Just like seatbelts, a policy doesn't have to stop every fatality for it to be wildly successful. Rust feels like 99% of my day-to-day errors in python just can't happen. Wonderful!
To think that I'd need Sponsorblocker for your videos. Bleh
I don't mind at all, I totally understand. However, I'm hoping to do these videos professionally, and the way to do that is through advertising. If I can get sponsorship from relevant companies (not squarespace lol) then I'll be able to dedicate more time to making these videos for you. That sounds like a win/win for everyone!
@@NoBoilerplate I'm of the opinion that people RUclips-ing fulltime ruined the feeling of the platform. It is very corporate and business now. It is hard to find users that make videos in their free time for fun, which was very easy years ago when RUclips was still a smaller platform where nearly nobody did it full time.
@@ironnoriboi I don't think this is the right way to look at it. Let me know your thoughts on this, which says it much better than I could: ruclips.net/video/sUsI6W7-d28/видео.html
@NoBoilerplate, here's what ChatGPT helped me make. Is there no crate already out there that does this?
use std::io::{stdout, stdin, Write};
use std::str::FromStr;
pub fn prompt(message: &str, validate: F) -> T
where T: FromStr,
T::Err: std::fmt::Debug,
F: Fn(&str) -> Result
{
loop {
print!("{}", message);
stdout().flush()
.expect("Failed to flush stdout");
let mut input = String::new();
stdin().read_line(&mut input)
.expect("Failed to read input");
let input = input.trim();
match validate(input) {
Ok(value) => return value,
Err(error) => println!("{}", error),
}
}
}
pub fn prompt_value(message: &str) -> T
where T: FromStr,
T::Err: std::fmt::Debug
{
prompt(message, |input| {
match input.parse() {
Ok(value) => Ok(value),
Err(_) => Err("Error parsing input"),
}
})
}
ChatGPT's fun isn't it! I also am using as part of my daily work.
HOWEVER this seems wildly complex. Do you really need all that generic stuff? I see you're more interested in the input side than the validate side (your code expects a validate function to be passed in)
There's lots of input crates available crates.io/search?q=input
What are you attempting to do?
@@NoBoilerplate With the FromStr (trait?) it allows for identifying a parsable type. That way you can simply do this:
mod prompt;
use prompt::prompt_value;
fn main() {
let age = prompt_value::("Enter your age: ");
println!("Your age is: {}", age);
}
It's nice because prompt_value won't return until it has a valid value. Command line apps are my way of experimenting.
@@urbanelemental3308 that's so cool! Turbofish is a DELIGHT
@@NoBoilerplate Ah yes. I love that iterables are seemly built in.
@@urbanelemental3308 oh I LOVE iters. Have you seen: crates.io/crates/rayon
7:00 how about this?
station.sections
.iter_mut()
.find(|m| m.name)
.expect("Section not found.")
.active = true;
perfect!
I think you want:
station.sections.iter_mut().find(|m| m.name == section).expect("Section not found.").active = true;
Thank you!
7:05 what about the Iterator::find method? You can just use a |m| m.name == section as the predicate...
nice!
You have an interesting channel. I just discovered it thanks to @codetothemoon
Thank you so much! My latest video has a shout-out to CTTM :-)
🦀
ferris!