Idiomatic Rust - Builder Pattern

  Рет қаралды 28,556

Let's Get Rusty

Let's Get Rusty

Күн бұрын

Пікірлер: 101
@letsgetrusty
@letsgetrusty 2 жыл бұрын
📝Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet
@redcrafterlppa303
@redcrafterlppa303 2 жыл бұрын
I was first introduced to the builder pattern in java. But in rust it really shines since it matches the language design better in my opinion.
@DipietroGuido
@DipietroGuido 2 жыл бұрын
That's coz Java is the worst programming language ever invented
@redcrafterlppa303
@redcrafterlppa303 2 жыл бұрын
@@DipietroGuido I disagree but if that's your opinion 🤷
@----__---
@----__--- 2 жыл бұрын
To be more idiomatic, I think the build method should have taken selfs ownership and consumed it. That way you wouldnt have needed to clone anything either.
@DrIngo1980
@DrIngo1980 2 жыл бұрын
Ideally I would not need an additional ServerBuilder struct. All those methods on the ServerBuilder struct could be directly implemented on the Server struct instead, and then instead of using `&mut self` everywhere use `Self`. This would mean those functions consume self and return a modified "new" self. Example: ``` struct TlsConfig {} struct Server { host: String, port: usize, tls: Option, timeout: usize, hot_reload: bool, } impl Server { pub fn new(host: String, port: usize) -> Self { Server { host, port, tls: None, timeout: 2000, hot_reload: false, } } pub fn with_tls(mut self, config: TlsConfig) -> Self { self.tls = Some(config); self } pub fn with_hotreload(mut self, hot_reload: bool) -> Self { self.hot_reload = hot_reload; self } pub fn with_timeout(mut self, timeout: usize) -> Self { self.timeout = timeout; self } } pub fn test() { let server = Server::new("abc.de".to_owned(), 8080) .with_tls(TlsConfig {}) .with_hotreload(true) .with_timeout(5000); } ``` A little less boilerplate. I am not sure if this is more idiomatic or not or if it is even correct to call this "builder pattern", since at no point I am calling a specialized "build()" function. Anyway, whatever the pattern is called that I used here, I kinda like it. What do you guys and gals think?
@LordOfTheBing
@LordOfTheBing 2 жыл бұрын
@@DrIngo1980 your approach requires mutation, and provides no guarantee that it is only used at initiation. what happens if someone changes those config later on at runtime? Going through the builder also ensures by design that the server itself will never need to be mutable.
@DrIngo1980
@DrIngo1980 2 жыл бұрын
@@LordOfTheBing Thanks for your comment. I am still learning Rust, so every insight that helps me better understand the language and the patterns (idiomatic or not), is greatly appreciate. So, yeah, my approach requires mutation ("mut self"), but the ServerBuilder struct's functions also require mutation "&mut self", don't they? But yes, I can see now how my suggestion allows changes at any time, while the ServerBuilder approach only allows changes at "construction time" of the object. Thank you for pointing that out to me. 🙂
@----__---
@----__--- 2 жыл бұрын
@@DrIngo1980 this could counted as builder pattern too I guess. I actually use this in a few of my library crates where the struct only have a few and optional fields. Its better to use another builder struct to ensure the functions cant be called again once you "build". The other thing is that it does a lot of moving of the ownership, that basically means doing many memcpy and memset s. I am not sure if they would be optimized away in all cases but most likely they are, so thats good.
@pwalkleyuk
@pwalkleyuk 2 жыл бұрын
@@DrIngo1980 In java its usually referred to as a fluent API.
@ixirsii
@ixirsii 2 жыл бұрын
Great video! I'd love if you could talk about project and file structure for larger projects. As a Java developer I'm used to packages and every file being nested 3 folders deep, every type having its own file, and tests being separate from implementation. AFAIK Rust doesn't do this but I'm still not really sure how to structure my projects.
@donwinston
@donwinston Жыл бұрын
These videos are excellent. Just the right length but effectively covers the current topic.
@saaddahmani1870
@saaddahmani1870 2 жыл бұрын
Really nice, .. more idiomatic rust please.
@MrYevelnad
@MrYevelnad 2 жыл бұрын
Wow, i been a fun of oop and this builder pattern is love at first sight. I always question my self why we need implements in every language and this explains all. Thank you brian.
@andymooseful
@andymooseful 2 жыл бұрын
Thanks for another great video. After watching a lot of your tutorials, and reading the rust-lang docs I was able to port a Scala microservice to Rust, and gain huge memory footprint reductions. I’m still struggling with the memory model though but it’s early days!
@MrEnsiferum77
@MrEnsiferum77 2 жыл бұрын
Can't go more wrong than this. Porting scala to rust and u even don't know to write rust properly and on top of that u changing microservice. Stick with one language with all microservices.
@yapayzeka
@yapayzeka 2 жыл бұрын
you deleted later but at 2:22 for the sake of the convention the alternative constructors should be named with_tls, and with_advanced. similar to Vec::new() and Vec::with_capacity()
@bayoneta10
@bayoneta10 2 жыл бұрын
I think that "new" method should be implemented in ServerBuilder and as you did, "build" method returns the Server instance. Thank you a lot for the content.
@bradywb98
@bradywb98 2 жыл бұрын
By convention, ::new is used to construct an instance of Self, and that convention is broken if ServerBuilder’s ::new returns a Server.
@bradywb98
@bradywb98 2 жыл бұрын
However, in his implementation, Server’s ::new returns a ServerBuilder so I’m not sure.
@ultimatedude5686
@ultimatedude5686 Жыл бұрын
I would either implement Server::new in ServerBuilder or I would rename the method to something like Server::new_builder.
@bjugdbjk
@bjugdbjk 2 жыл бұрын
Thank you so much for the Github link, That was only thing missing in this wonderful resourceful channel !!
@nathanbanks2354
@nathanbanks2354 2 жыл бұрын
I'm used to the builder pattern in other languages. I like it--tough to build, but easy to use. Then you mention the derive_builder macro (7:25), and I start to realize why Rust is so popular.
@soberhippie
@soberhippie 2 жыл бұрын
Two questions: is it a good idea to call a method that returns a builder instead of an instance of Self, new? Wouldn't it be more expressive to call the method `builder`? Server::builder(),tls().build() looks more understandable to me. The other question is why not just use setters on a Server instance? Is it mutability or something else?
@Luxalpa
@Luxalpa 2 жыл бұрын
The reason for not using setters is because it's more convenient to chain the builders. With setters you would have a bunch of lines like server.setTLS(...); server.setTimeout(...); ...
@gvcallen
@gvcallen 2 жыл бұрын
I'd definitely rather have a non-associated method for the builder as "new" general implies that the type is it impl on is being returned
@penguin_brian
@penguin_brian 2 жыл бұрын
The feature of this approach is that it enforces that all values must be set before returning the object. At compile time. Where as the setters approach allows changing the values at any time. Which may or may not be appropriate. E.g. if the server is already running, you may not want to support changing the certificate. Setters could also have a runtime error, but I think a compile time error is better if this is possible.
@dmdeemer
@dmdeemer Жыл бұрын
It seems like the builder pattern is meant to be combined with the type-state pattern. Like you have a "building" state that allows you to mutate the optional items, and then you call .build() to get the "finished" state. If you want to, you can add other states, like "connected" and "disconnected". Example (sorry for any typos or syntax errors): enum ServerState { Building, Finished }; type ms = u32; struct Server { host: String, port: u16, tls: Option, hot_reload: bool, timeout: ms }; impl Server { fn new(host: String, port: u16) -> Server { /* ... */ } } impl Server { fn tls(&mut self, tls: TLSCert) -> Self { self.tls = Some(tls); &mut self } fn hot_reload(&mut self, hot_reload: bool) -> Self { self.hot_reload = hot_reload; &mut self } fn timeout(&mut self, timeout: ms) -> Self { self.timeout = timeout; &mut self } fn build(self) -> Server { /* ... */ } }
@jonathanmoore5619
@jonathanmoore5619 2 жыл бұрын
As always, thks. Simple concept... Explained very well.
@jamesjenkins9480
@jamesjenkins9480 2 жыл бұрын
Oh nice. I'd not realized how this was done before. Great concise video!
@marcobuess
@marcobuess 2 жыл бұрын
Is there a specific reason to encapsulate the functions in ServerBuilder instead of Server directly?
@gvcallen
@gvcallen 2 жыл бұрын
Usually, the idea is that the Builder doesn't have all the information to represent the State fully/correctly, but the build method creates the State and changes types to ensure correctness. In this example it is trivial, since the optional values have reasonable default, but in more complex builders, various checks and configurations may need to take place in order to build the State
@marcobuess
@marcobuess 2 жыл бұрын
@@gvcallen Why even have the build method altogether? Why not just have the methods be more like a Server -> Server signature and mutate the field's as needed? So you could so something like Server::new().withPort(1234)...
@supersonictumbleweed
@supersonictumbleweed 2 жыл бұрын
@@marcobuess because if you wanted a field that is settable only once you'd have to have the methods throw after initiialization which is a bit unweildy.
@gvcallen
@gvcallen 2 жыл бұрын
@@marcobuess In this case the builder is acting like a constructor. This allows certain aspects of the Server to be "locked in", like the port number, which cannot be changed while the server is up and running. Another example I can think of is embedded - you can "build" a struct containing IO information (such as pin numbers) but you often want that IO to be in a locked, built state once configured. It also allows separation of concerns when mutating the Server - what if a function like with_home_page(...) allowed you to specify a home page for the server via the builder, but if no home page was provided then one had to be fetched from a database for example. Using the builder, this configuration can be done right before building the server (like a constructor with optional fields). With a server, this would need to be done every time on startup. Also, the code is neater with all the constructor impl in one place. There are other examples on the web of why the builder pattern (not rust specific) is good. Hope this helps
@epiderpski
@epiderpski 2 жыл бұрын
These are really helpful. Please keep doing these idiomatic Rust vids.
@flippert0
@flippert0 Жыл бұрын
As far I understood the docs about "derive_builder", tuple structs and structs with generics are not supported? That leaves this crate for simple data objects.
@phenanrithe
@phenanrithe 2 жыл бұрын
*Overloading* function and *default parameters* are two functionalities that are dearly missed in Rust. It's sad that it takes years to make any decision when it's so easy to add, here and in similar cases, because it really impedes the language.
@RodrigoStuchi
@RodrigoStuchi 2 жыл бұрын
absolutely awesome content 👏👏, more idiomatic Rust pls
@bradywb98
@bradywb98 2 жыл бұрын
How is ServerBuilder’s build method able to initialize a Server like that, given that Server’s fields are not public?
@gilatron1241
@gilatron1241 2 жыл бұрын
I really like this builder pattern
@daque1960
@daque1960 2 жыл бұрын
Thanks again for all the great work. While doing Rustlings I came across a match statement containing binder patterns using @. Is this idiomatic? match (slice[0], slice[1], slice[2]) { (r @ 0..=255, g @ 0..=255, b @ 0..=255) => Ok(Color { red: r as u8, green: g as u8, blue: b as u8, }), _ => Err(IntoColorError::IntConversion), }
@joelgodfrey-smith3655
@joelgodfrey-smith3655 2 жыл бұрын
Great video, as always!
@rregid
@rregid 2 жыл бұрын
I'm trying to learn Rust as it looks nice and perfomant but addition of some quality of life things like default argumets would make it so much easier to learn and write
@DrIngo1980
@DrIngo1980 2 жыл бұрын
While I do love Rust, I agree with this. I'd love to have optional default values for arguments as well.
@redcrafterlppa303
@redcrafterlppa303 2 жыл бұрын
The problem with default values is, that they can make a function difficult to call quickly. Function overloading is the same in green. I think by not including both developers are pushed to use cleaner solutions like the builder pattern. I first started with Java which has constructor and function overloading and I used it often. But today for any more complex class I try to use some sort of building pattern instead of a public constructor.
@----__---
@----__--- 2 жыл бұрын
@@redcrafterlppa303 Optional arguments being not included in the language is not a design choice by the devs. It just needs to be implemented cleanly without breaking type inference and whatnot.
@DrIngo1980
@DrIngo1980 2 жыл бұрын
@@redcrafterlppa303 The problem I see when not having optional default values for function arguments is that you end up with a lot of `Option` kinda properties/member variables that you then have to check for Some/None later on. Which makes that part of the code a bit more convoluted. Well, you could have "some_arg.unwrap_or()" instead, sure. It's just that in my eyes it "looks" a bit "uglier" when reading code. That's really it for me. Also, I am not sure I understand your first argument here: > "The problem with default values is, that they can make a function difficult to call quickly." Having default values for function arguments makes calling a function difficult to do quickly? What do you mean by "calling a function quickly"? Could you elaborate, please? I'm pretty sure I am missing something and your argument will start to make sense to me once I understand what you mean by "call a function quickly".
@rregid
@rregid 2 жыл бұрын
@@redcrafterlppa303 perhaps you're right here, Im coming from c++/java/python background and there are some concepts in rust that from the starl look kinda alien. As for speed and safety - I guess a bit more complexity while coding is nessecery tradeoff for speed and safety and I'm not yet used to the style)
@strangnet
@strangnet 2 жыл бұрын
Is it really idiomatic to not return Self for a new function for the Server struct? I would have to know about the ServerBuilder implementation and its build function to be able to use the Server struct.
@----__---
@----__--- 2 жыл бұрын
To be able to use Server struct you need to know about its implementation too? Thats basically builder pattern.
@strangnet
@strangnet 2 жыл бұрын
@@----__--- sure, but the semantics of returning a ServerBuilder on Server::new() is convoluted. Why not just work with ServerBuilder from the start?
@LordOfTheBing
@LordOfTheBing 2 жыл бұрын
@@strangnet you can start from the builder, and that is often done, but isn't deemed very user-friendly. people want the Server, so having the api designed such that the object of interest already guides them to the proper pattern without having to dig into documentation to know what extra hoops to jump through is much more helpful.
@phenanrithe
@phenanrithe 2 жыл бұрын
The builder/fluent pattern is a nice way to create objects, but in Rust it suffers from life-of-time analysis constraints that goes in the way. It's not possible to interrupt the flow and continue it, for example in an if/else construct, which is a frequent case, because the lifetime of the reference dies immediately when the build() is not included. Unfortunately I haven't found a satisfactory solution yet (moving the value generates a lot of other problems). To cope with that, weird shadowing techniques must be used, but it's once more a problem for code clarity.
@itsdazing1279
@itsdazing1279 2 жыл бұрын
Nice video 👍
@RoamingAdhocrat
@RoamingAdhocrat Жыл бұрын
I don't love `unwrap_or_default` - imo `unwrap_or(false)` would be clearer that the param defaults to false
@a_maxed_out_handle_of_30_chars
@a_maxed_out_handle_of_30_chars Жыл бұрын
thank you :)
@ksnyou
@ksnyou 2 жыл бұрын
Nice! Next spring for rust :)
@ДаниилРабинович-б9п
@ДаниилРабинович-б9п 2 жыл бұрын
wouldn't it be better to have the build method take ownership of the builder functions take ownership of self instead of introducing a layer of indirection with &mut? And the .build() could take an immutable reference, if you want to reuse the builder.
@joelmontesdeoca6572
@joelmontesdeoca6572 2 жыл бұрын
This one was great!
@felixst-gelais6722
@felixst-gelais6722 2 жыл бұрын
What would happen if the fields on Server were private? Because then ServerBuilder couldnt just call the constructor on Server
@LordOfTheBing
@LordOfTheBing 2 жыл бұрын
the fields on Server already are private in the example. the ServerBuilder can create the Server without going through extra custom constructors because they're in the same module.
@bezimienny5
@bezimienny5 2 жыл бұрын
@@LordOfTheBing and thats the beauty of rust for you
@Xeros08
@Xeros08 2 жыл бұрын
Question about Trait objects in Rust. In the Vec, is the Box really needed? Asking because on your Java OOP vs Rust Trait Objects, you showed the example of a Vec and that cunfused me a bit. Anyways, great videos, really liking the Idiomatic Rust series :D
@agfd5659
@agfd5659 2 жыл бұрын
Yes, not necessarily Box, but some pointer is needed because the inner type's size is not known, whereas a pointer's size is known. dyn Animal is not Sized, i.e. its size is not known at compile time.
@Jordan4Ibanez
@Jordan4Ibanez 2 жыл бұрын
I love your videos. Have you thought about uploading to odysee as well? You can automatically upload there without thinking about it once you set it up
@PokeNebula
@PokeNebula 2 жыл бұрын
Why not make it so that server only had the advanced constructor, but just make it take Option values for each of the optional parameters, then inside the constructor, unwrap or default each of the optional values? Instead of the programmer using another struct, the programmer just has to either wrap some paramaters in Some(), or just say None a few times.
@mod7ex_fx
@mod7ex_fx Жыл бұрын
great video thanks
@cjt9150
@cjt9150 2 жыл бұрын
I found that: something about extension traits FileName: sample\src\lib.rs ---------------------------------------------- pub trait Core { fn core(&self) -> String; } FileName: sample\src\main.rs -------------------------------------------------- use sample::Core; trait Derived: Core { fn derived(&self); } pub struct Sample { pub name: String, } impl Core for Sample { fn core(&self) -> String { "core".to_owned() } } impl Derived for Sample { fn derived(&self) { println!("{} & derived", self.core()); } } fn main() { let s = Sample { name: "name".to_owned() }; s.derived(); }
@mojacodes
@mojacodes 2 жыл бұрын
so?
@NareionSD
@NareionSD Жыл бұрын
This would be an ok demostration of the builder pattern if you hadn't changed the Server::new function. You not only overrode an idiomatic function from another video, but also made a function which name lies to you (you would expect a "new" Server to be a Server, not a builder). You could have easily just made a Server::builder() function and either left Server::new as is (having then a factory method as an option) or erased it completely.
@davidvernon3119
@davidvernon3119 2 жыл бұрын
I am very new to rust, but this seems like a lot of hocus-pocus just to make rust seem dynamic when it (intentionally) isn’t.
@GlobalYoung7
@GlobalYoung7 2 жыл бұрын
thank you 😊
@stefankyriacou7151
@stefankyriacou7151 2 жыл бұрын
Why did the build method need a mutable reference to self, could it not have been immutable? It doesn't seem to be modifying any of self's attributes?
@dimitrobest5293
@dimitrobest5293 2 жыл бұрын
awesome
@ErikBongers
@ErikBongers 2 жыл бұрын
I don't understand why this would be better than overloaded functions. So much boilerplate. The fact that there's a macro for this, kind of proves the point.
@saadisave
@saadisave 2 жыл бұрын
Better than overloaded functions because: 1. a single signature for each function makes it far easier for the compiler 2. no complicated algorithms required to determine which function to call 3. Easier for dynamic libraries 4. Rust is not Java
@----__---
@----__--- 2 жыл бұрын
Do you really think the existence of every convenience third party library proves that the feature needs to be embedded in the language. Rust's type system is powerful enough that basically renders the overloading unnecessary. It is also confusing, in docs and whatnot, which makes not including it favourable against ergonomics. We need named arguments and defaults though but those are hard to implement without breaking type inference and not bloat the language like in c++'s case.
@G11713
@G11713 2 жыл бұрын
Great point. I find Rust verbose though the more I learn the less annoying it feels and the presence of the macro system does express a systematic way to eliminate verbosity. Ultimately, we may get macros of the form: sql! { some SQL syntax } haskell! { some Haskell syntax } fsharp! { some F# syntax } dented_rust! { indent sensitive rust syntax ... get it, dented mental } It is a brave new world... perhaps.
@ErikBongers
@ErikBongers 2 жыл бұрын
Have thought about this a little more, and as often with examples, this Builder pattern is a bit overkill in this case. But the pattern itself is quite useful. As someone pointed out here, it's mainly the lack of default values that is a pain. The lack of overloading is less of an issue as overloading hides what is happening while functions with names like new_this() and new_that() improve readability, as it's clear what's happening.
@redcrafterlppa303
@redcrafterlppa303 2 жыл бұрын
I just realized how waste the gui api world is in rust. Shure some exist, but they are either very high level designed or very barebones. I decided that I will create my own api for guis for Windows. Maybe a cross platform version will follow but don't quote me on that.
@donwinston
@donwinston Жыл бұрын
Who decided the standard style for writing Rust code for functions is xyzzy_abcd() and not xyzAbc()? Underscore characters in names is ugly.
@aftalavera
@aftalavera Жыл бұрын
To be even more idiomatic and realistic, dump Rust!
@minatonamikaze2637
@minatonamikaze2637 Жыл бұрын
L
@cjt9150
@cjt9150 2 жыл бұрын
Doubt: what is extension traits? docs.rs/rand/0.8.5/rand/trait.Rng.html, it says "trait Rng is automatically-implemented extension trait on RngCore", can we extend trait?
@mojacodes
@mojacodes 2 жыл бұрын
basically a trait that extends another trait's functionality in specific types, or in this case all types that implement the original trait. a bit like subclassed in OO languages.
Idiomatic Rust - Iterating over Option
5:27
Let's Get Rusty
Рет қаралды 25 М.
Idiomatic Rust - Constructors
13:10
Let's Get Rusty
Рет қаралды 41 М.
VIP ACCESS
00:47
Natan por Aí
Рет қаралды 30 МЛН
人是不能做到吗?#火影忍者 #家人  #佐助
00:20
火影忍者一家
Рет қаралды 20 МЛН
Enceinte et en Bazard: Les Chroniques du Nettoyage ! 🚽✨
00:21
Two More French
Рет қаралды 42 МЛН
Rust Programming: TypeState Builder Pattern Explained
14:30
Jeremy Chone
Рет қаралды 35 М.
Improve your Rust APIs with the type state pattern
14:45
Let's Get Rusty
Рет қаралды 92 М.
How to make your malware HARD to detect
17:21
Mitch Edwards (@valhalla_dev)
Рет қаралды 9 М.
My favorite Rust design pattern
7:00
Let's Get Rusty
Рет қаралды 44 М.
Idiomatic Rust - Function Arguments
8:42
Let's Get Rusty
Рет қаралды 25 М.
Rust & Wasm
9:38
No Boilerplate
Рет қаралды 201 М.
Fluent Builder in Python: Building Objects with Elegance
20:54
campbelltech
Рет қаралды 2,1 М.
Rust's Witchcraft
9:18
No Boilerplate
Рет қаралды 190 М.
8 deadly mistakes beginner Rust developers make
14:14
Let's Get Rusty
Рет қаралды 178 М.
VIP ACCESS
00:47
Natan por Aí
Рет қаралды 30 МЛН