The issue with massive where clauses can be solved with traits and associated types. All the generics that are in the where clause on the struct can be put into a trait with all the required super traits. This makes you have only a single generic in your where clauses. DRYS
I don't get the obsession with OCaml. It doesn't really solve any of these issues. It just forces you to write your program in a different way. The author decided to use static dispatch for the performance benefits. However, rust with dynamic dispatch would still be much faster than OCaml. Also, the biggest issue that the author made was assuming that static dispatch would improve performance. Generally, this is true, but depending on the specific use case the improvements could be negligible. Using Arc is ok. Using Clone is ok. Using dyn is ok. Never optimize before profiling.
Yeah trying to overoptimize is a mistake people seem to make a lot. I mean, this thing is doing IO with a database, it's not some game engine trying to get more frames. The cost of making it a dynamic dispatch is going to be negligible. Chasing 100% optimization is rarely worth it.
@@guguluduguluit's a trait that acts as a tag. You define an essentially empty trait that requires all those constraints then you constrain by requiring things to have this trait implemented.
@@guguludugulu xvxvxvxv is correct, I just mean a trait that serves as a tag. It has no methods, a bunch of trait bounds, and has a blanket impl for the types with those trait bounds. In that way, requiring that trait in a trait bound will automatically imply all those other trait bounds for free.
....I'm afraid to say I've wrangled much worse in Rust. There is a lot you can do to compose traits and fold away the complexity. Composition is the correct tool to fold up complexity, assuming you can factor things correctly.
I don't know rust, but I'm pretty sure there is a way to not repeat all those traits bounds. Like define one trait that depends on all the rest, or use a type alias.
wouldn't something like a helper trait clear a lot of the noise? in the like of trait C: A + B + Clone + Send +Sync+ 'static{} impl C for T where T: A + B + Clone + Send +Sync+ 'static {} then it is just fn foo(x: impl C)
Solving this is actually quite simple. Define a trait `IsDriver` implement it for driver if and only if all the where clauses hold. Use `where Driver: IsDriver`. That makes it a lot better and it's not that odd of an idea i think.
8:00 There are things you could do to avoid this. 1. Define trains for all of these lines, which are automatically implemented on everything 2. Make a trait ("Traits") to include all these traits (trait Trais { type A: AbusePolicy + Clone + ...; type C: Clock + Clone ...; ... }) 3. type SimpleDriver = Driver;
Just have a named thing that represents the repeating stuff. It's the general solution to avoiding repetitions. People figured that out more than 10000 years ago. And most languages allow you to name most things. In this case Rust does have a way of naming this mess by creating a trait to contain the generic vomit. So the API is only exposing an object with a trait for that object. Even if you end up chaining many APIs, at the highest level you would still end up with a small number of traits that contain all the messy stuff inside them. But that applies to everything else as well. You write a single word to call a function that may contain millions of lines of code. You use a single word to create an instance of a class that may contain millions of members in the tree of objects it contains. But you don't care about what's inside. You just care about the interface.
ปีที่แล้ว +1
Exactly. Although nothing prevents you to go crazy spreading the generic vomit all over the place. It does not mean that you have to. A trait per generic vomit could be an option here.
8:50 Yes, this is ugly and I would love it when Rust trait bounds would propagate automatically, but until that is implemented it is actually possible to use a mix of super traits and generic trait implementation to basically define trait aliase. That way the bounds wouldn't have to be copied and changes would be much easier to do. So yes it is ugly and it would be nice if Rust had better support for these cases. But there are solutions!
in c++ there's a dirty trick to 'solve' the template constraint thing: make an abstract interface and then check if the type you require derives from that interface. It doesn't have to make virtual calls, since you use the derived type as is, and it's sort of simple to migrate from dynamic dispatch to static. And since you can combine interfaces, checking if your type corresponds with the exact behavior you need is trivial: *template requires std::derived_from*, or even *template*
struct definitions should (almost) NEVER contain bounds on generics. Trait bounds define behavior, where a struct is a definition of memory layout. The moment you put a bound on a struct... you are obligated to include it on every impl block. impls define behaviors, not layout. Behaviors are defined by their bounds. I say "almost" because some marker traits (I believe just "Sized") makes sense to include on a struct, because Size is closely related to layout rather than behavior. Somewhere there is a github issue of David Tolnay arguing to include a lint in clippy for this exact thing. And tbh, that lint would have saved me soooo much time when first learning rust.
I see a lot of people saying "you can simplify it with a supertrait that combines all the others"... There's yet another solution too, why not just put all the possible drivers in an enum? Sure, there'll be one extra branch compared to the completely template solution, but: * it's easily extensible * you can use a crate (I forget the name but I know it exists) to automatically implement a trait for an enum where all the variants implement the trait too, so you can just use it like a driver with no source code changes * if driver doesn't change at runtime, all the branches will be predicted anyway, the overhead will be minimal And you can wrap up all the trait bounds required just at the enum definition!
This is the comment I was looking for! "Dynamic" dispatch doesn't have to be a magic abstraction like a trait object, you can just make a mini abstraction to solve your usecase without losing most of the benefits of not using runtime type objects.
I interface with a large corporation that implemented their services in Go, using Rust on my end. I can say that the holes in their Go implementation are glaring. Don't use Go for an REST API type thing. There are all kinds of logic errors that Rust would've prevented
its so interesting to me that people think "I want this complex ass thing to be perfect at compile time" and then do 1) the most naive solution possible, 2) spend no time making it better, then 3) revert everything and praise the original design. Happy for the developer that their codebase is better, but seems like they didn't even know what they wanted. Nonetheless, playing with rust types is fun so I don't blame the naive/intuitive refactoring approach.
I'd argue that sharing a transaction is just hard regardless of the language. Especially when it involves some kind of distributed commit bollocks that i'm sure works fine except when it doesn't (which, spoiler alert, is exactly the cases where you care about it in my experience). In the end, having to use multiple transactions in a code base (or module depending on your architectural bent) seems like a huge smell to me. Having said that just like all code smells, that doesn't mean it's not actually necessary. Also, I haven't dug around the III-IV code enough but I wonder how much of this is just down to things being included that shouldn't be the database driver's problem. Not that i'm ragging on the guy if that is the issue... keeping a design clean like that is a herculean task. Also, i'd probably set the general rule to be using dynamic dispatch and save static dispatch for places where it really matters which i'd argue it generally doesn't if your context is Web API endpoints talking to a database. You'll spend longer on protocol management and Db IO than you ever will on dynamic dispatch. (which is pretty much what you said)
In server code to avoid lifetimes just wrap your stuff in Arc. Or any other smart pointer of your choice. In IO bound context like web it would not matter and you have no silly lifetimes.
I have no experience with rust, but could you not have an interface (or 'trait'?) that was declared to extend those other traits? seems like you could and just declare that trait instead of having to list every trait it 'extends' in every function that uses it. Even if it's a generic. I don't get it
Often inlining will be for a single use, which results in sightly less overall code (a few bytes for the call and function prologue), and the real value is when the inlining lets the optimizer take advantage of local conditions to simplify the inline code, so it's not always an increase in size. But you're generally correct!
This isn't inherently an issue about static dispatch at all. However, doing static dispatch right AND ergonomic is pretty hard in some circumstances like this, and to have a truly ergonomic solution you need to either dive into the rustnomicon and carefully write some unsafe bits, AND/OR get to writing some macros.
Why not create new super Traits like `Trait A: Clone + Send + Sync {}` You would still have to specify it but surely writing where `B: A` is less atrocious. Am I missing a downside of such an approach?
It could be an issue if functions do not share trait bounds aka function 1 needs traits A B C but function 2 needs A B D. Composition can still help, at the very least in the example code & what we can infer their codebase to look like with the clauses "repeated". A different issue (that probably isn't what the developer was facing) could be that you may want additional/different function-specific constraints per function for preventing misuse of the function, though in rust this is typically more of a stylistic choice than a need, since this prevention of code smell is better solved using the compiler as a way to get the naive solution and then using lints or reviewing your code to optimise later.
It’s the same with templates in C++. Using dynamic dispatch (virtual methods through interfaces) is often easier, cleaner and compiles faster even if it has a runtime cost. Don’t invoke a dynamic dispatch method to write a single byte, instead pass it a whole buffer and the vtbl-jump penalty becomes negligible.
Yes, it is bigger not smaller (but faster in runtime). However in C++ you can use an interface (just for the type, no virtual methods) and then set a requirement in the derived template. All static checked and super easy to implement with respect to Rust.
I have seen in the comments way to massively simplify the issue, but rust is also working on implied trait bounds which would eliminate the need for most where clauses in this code
yeah Rust could really use some more inference tools where you can just type a keyword and just tell the compiler to figure it out. Even if it was just a relative type definition, that would be good. Like "typeof Argument1(f)" that way if you ever changed the type of f's argument, it would propagate.
My biggest problem with Go is the error handling. If it had pattern matching like Rust I'd probably use it more. Due to your shilling, I'm going to have to check out OCaml.
Also in most cases the correct answer would be to use an enum if what you are developing is not a library. Even if it is a library there is a real benefit to put all the variants you define yourself into an enum and having an `Other` variant which contains the `dyn`.
That's why you don't pass the DI struct around directly; you define a trait, turn all the parameters into associated types, implement it, and then pass down an `impl FooContext`. This is symmetrical to how you'd define DI in C# or Java, only without dynamic dispatch.
this is what OO would look like if it didn't have composition or types (aka not OO). The comparison doesn't work! I know you were probably joking, but I wanted to agree before thinking about it lol
i use cpp if I need maximum performance, rust if I need high performance or I want to use rust, and c# if I don’t care about performance at all. But inevitably I always regret using c# because i want more performance later down the road
Is there any world where the speedup from static dispatch to relational database calls is relevant when compared to the time spent in IO for those calls? Asking for a friend…
if you would go for the static dispatch , and utilize a bunch of super traits , it is better to create macro to avoid the disaster of having to write all implementations. If u wanted performance then let the compiler do the work for you… at the expense of longer compile times, but you can opt to expand those macro later on if u wanted to…
Prime is a pretty dramatic guy. He couldn't stop shilling Rust when it was new to him and now he can't stop shilling the new shiny thing he can't see negatively. Now that he's over Rust as the One True Language he can view it more realistically, but goes a bit further and views it negatively to contrast it with his feelings for other languages. To me what makes Rust shine through his perspective only is how much he has to admit it's still super good despite it maybe not being deity-status, contrasted with how he doesn't really talk about zig that much anymore since trying/"moving onto" OCaml
...It's safe because it makes it impossible to try and do things that haven't already been done... the framework achieves UNLIMITED SAFETY despite being written entirely in gotos! It's perfect safety record is compounded by this decision as most developers refuse to even CONSIDER working on systems that use just one goto that's not next to a token called "while" or "void*". Arbitrary semantics implemented as macros are tolerated giving the framework infinite extensibility that's NEVER USED, further contributing to its overall safety.
Ultimately it seems like the only reason they need to change the database is so that they can use sqlite in tests, apart from that, using just Postgresql is fine. I hate to be the TDD guy, but this is the issue with testing after writing the code, you can build something that is so tightly coupled to a system that shouldn't be in tests that it turns into this when you actually get around to testing. If this was built up with tests using sqlite and units using postgresql, piece by piece, it could have built up a must less leaky abstraction. It still could have become this, but the issues would have shown up sooner where it would have been easier to fix.
I wrote something like this like 2 weeks ago but ended up refactoring it just fine. But I do agree there's no clear way to refactor the number of generics in a thing both up and down in rust. That's super annoying and I hope something like trait bound alias can land. Before that some kind of macro can probably help in some situations.
What do you mean by "trait bound alias"? Like the "type" keyword? I guess you can't use type for traits like you can for structs/enums (since they aren't types in the same way), but you can use trait constraints to bundle them together, and it feels pretty similar: "trait C: A + B { }"
Once put two days of work into setting up icinga... took me a day to get it running stable in docker (first time)... then i saw the configuration and dropped it almost instantly xD Sunken cost fallacy my ass :)
If you are trying to make something this complex, sharing transactions, extended traits used on several layers... Static dispatch is simply not the right choice. Don't make it sound like static dispatch its the only right way. Dynamic dispatch is actually the most common way how every other language does this. It is perfectly fine. Rust just gives you the rare option to create a static source from generic functions in case it is possible.
Not sure why the author thinks modern C++ is deranged but this is not. Rust is usually great, but I’ve never had to write monstrosities like that in C++.
> `Arc` Am I the only one who uses feature flags and conditional compilation to do mocking in Rust? I detest having to change the way my code is written just to accommodate mocks
trait composition (written like "trait C: A + C { }") is like trait 101 and its pretty interesting that the author doesn't mention it or isn't familiar with it
This is the worst thing about Rust in my experience, its a leaky language, in the sense that implementation details of your types and modules easily explode into a mess across the entire program.
you can also constrain the method to the trait required like hashmap does (you don't need a bunch of traits to define all its behavior, just on the methods you use)
@@ThePrimeTimeagen Very good point! Though that being said, if your trait has a method that takes a generic parameter, it won't be considered object-safe anymore (so, no Arc). I'd rather prefer something like this: trait Foo {} trait Bar {} trait Baz {} trait Qux {} trait DoesItAll: Foo + Bar + Baz + Qux {} impl DoesItAll for T where T: Foo + Bar + Baz + Qux {} Then you can just do a "where T: DoesItAll" on your generics and boom, you've reduced the where-clause-boilerploooting. Rust does actually provide a lot of ways to reduce complexity, but unfortunately those are hard to come by when you've already coded yourself into a corner.
@@azratosh Your method is good and it has its place, for example when you do have unsafe code and you need to uphold some invariants no matter what. But for that use case it might be an overkill. About the type safety of constraining methods to traits. Rust will not let you exhibit behavior (call a method) if traits are not satisfied (for example, call `.clone()` if trait is not `Clone`, so it is absolutely type safe to, for example, have a struct that should be `Clone` but to not restrict it's generic bounds at the definition points, but rather implement `Clone` where all generics are `Clone`. Then, at the point where you need it to be `Clone`, constrain. That will save a lot of boilerplate.
@@ddystopia8091 Oh yeah, totally. I would never constrain the T on a Struct directly (unless really necessary); only on the impl blocks. Keeps everything tidy and modular.
I'll get you one better: there isn't anything that is universally true/perfect for everything, it's just a matter of tradeoff scale along with what tradeoffs you care about and what tradeoffs you don't. also @agcwall misunderstanding the original comment and acting like c++ is uniquely offering Perfect Zero-Cost Abstractions in this regard is pretty funny considering the subject matter
I do appreciate rust on a personal level, I am not a rust shill, but please don't take this as a bad faith criticism. this is a genuine comment/question: I am relatively new to your channel, and you seem like someone who tries to see things in a logical way, but when it comes to rust, it seems like you want to see it fail. I'm curious to understand why that would be the case.
@9:30 This where could be shorten and DRY-ed by supertraits... Guy don't know Rust well and trying to implement concepts from another languge. I saw the same with guy who was c++ programer and make presentation about how he tried to port his app to rust. When he wrote that sql driver spoils rest endpoints that was clear that design is poor and/or cargculted from other langage
@@RenderingUser JS and Rust have regular and async functions. You can call regular functions from async but not vice versa. Distinction with weird asymmetry.
@@gritcrit4385you *can* call sync from async, but you need to be careful it won't block, and you *can* call async from sync, but not then get the result without higher level coordination.
I believe zig has that ability (when the async stuff is actually included in a release lol) coroutines in lua are made from normal functions w/ the ability to yield from any function in the coroutine (anywhere in the stack), not sure if that counts.
Stupid youtube bot deleted my comment because of a link Here's rust playground gist: 4ce0f67a1cc096b217dea252465059e3 The point is that you can specify all you trait bounds in a single trait with associated types. And leaking abstractions is not a "Rust issue", it's a skill issue
Child exploitation is wrong absolutely therefore I'm a sith and proud of it. The "dark side" of the force allows one to shine more brightly imo. Good vid PrimeTime. Ethical appeal 11/10. From the vid I see that setting up domains and policies are the real issue where Go and others are merely a way to develop (like DB transactional interfaces) without getting involved with policy definition which is a double edged sword; laziness being the edge facing the wielder. Ignorance regarding Rust policies is valid but understanding one's own code and policies regarding interfaces would make it far easier to query the community and documentation relationally, so... Not advocating for or against Rust and others but many arguments/bullets seem like a one and done throwing in of the towel many times regarding Rust development. Tech officers and testers are generally going at languages with a bit more openness and curiosity as well as a deeper understanding of things of such a magnitude require the adequate level of consideration opposed to a quick "deep dive" considering something as monumental as a total migration. Looking back it feels as though I'm regurgitating what you've stated and wonder if those "devs" are listening to your advice because you sound familiar to a CTO. Good stuff.
The issue with massive where clauses can be solved with traits and associated types. All the generics that are in the where clause on the struct can be put into a trait with all the required super traits. This makes you have only a single generic in your where clauses. DRYS
Yeah, had a little WTF moment when he mentioned having like 34 copies of it across different files
Just came to say this, but it seems I don't have to. Traits can still be a mess, especially when mixed with lifetimes.
had this exact same thought
Yeah wanted to say this too
There's a lot of places you can't do that yet, e.g. inherent associated types
I don't get the obsession with OCaml. It doesn't really solve any of these issues. It just forces you to write your program in a different way. The author decided to use static dispatch for the performance benefits. However, rust with dynamic dispatch would still be much faster than OCaml.
Also, the biggest issue that the author made was assuming that static dispatch would improve performance. Generally, this is true, but depending on the specific use case the improvements could be negligible.
Using Arc is ok. Using Clone is ok. Using dyn is ok. Never optimize before profiling.
Yeah trying to overoptimize is a mistake people seem to make a lot. I mean, this thing is doing IO with a database, it's not some game engine trying to get more frames. The cost of making it a dynamic dispatch is going to be negligible. Chasing 100% optimization is rarely worth it.
This is where you use a tag trait specifically so you don't have to repeat the million where clauses every time. Skill issue.
I was thinking God isn't there a macro or something
@@nevokrien95 God should create a macro for it.
What's a tag trait? Google doesn't show anything but the docs for traits
@@guguluduguluit's a trait that acts as a tag. You define an essentially empty trait that requires all those constraints then you constrain by requiring things to have this trait implemented.
@@guguludugulu xvxvxvxv is correct, I just mean a trait that serves as a tag. It has no methods, a bunch of trait bounds, and has a blanket impl for the types with those trait bounds. In that way, requiring that trait in a trait bound will automatically imply all those other trait bounds for free.
....I'm afraid to say I've wrangled much worse in Rust. There is a lot you can do to compose traits and fold away the complexity. Composition is the correct tool to fold up complexity, assuming you can factor things correctly.
Dude releases videos faster than Netflix series.
that not hard, as we get a new show 2 times per year, in my country
I don't know rust, but I'm pretty sure there is a way to not repeat all those traits bounds. Like define one trait that depends on all the rest, or use a type alias.
Anyone who knows Rust better than OP or I able to confirm? Really curious.
@@Xevion really easy.
The syntax `trait Foo = Send + Sync + 'static;` is nightly, but you can do
trait Foo: Send + Sync + 'static {}trait Foo: Send + Sync + 'static {}
impl
wouldn't something like a helper trait clear a lot of the noise? in the like of
trait C: A + B + Clone + Send +Sync+ 'static{}
impl C for T where T: A + B + Clone + Send +Sync+ 'static {}
then it is just
fn foo(x: impl C)
exactly
@@AndrewBrownK It is called supertraits .....
doesn't the first #[async_trait] introduce dynamic dispatch instantly again, making this whole thing kinda moot?
astute observation
Solving this is actually quite simple. Define a trait `IsDriver` implement it for driver if and only if all the where clauses hold. Use `where Driver: IsDriver`. That makes it a lot better and it's not that odd of an idea i think.
you can constrain traits to methods too. hashmap does this
8:00 There are things you could do to avoid this.
1. Define trains for all of these lines, which are automatically implemented on everything
2. Make a trait ("Traits") to include all these traits (trait Trais { type A: AbusePolicy + Clone + ...; type C: Clock + Clone ...; ... })
3. type SimpleDriver = Driver;
Thank you. It's so obvious, but I had not thought about doing it this way.
Just have a named thing that represents the repeating stuff.
It's the general solution to avoiding repetitions. People figured that out more than 10000 years ago. And most languages allow you to name most things.
In this case Rust does have a way of naming this mess by creating a trait to contain the generic vomit. So the API is only exposing an object with a trait for that object. Even if you end up chaining many APIs, at the highest level you would still end up with a small number of traits that contain all the messy stuff inside them. But that applies to everything else as well. You write a single word to call a function that may contain millions of lines of code. You use a single word to create an instance of a class that may contain millions of members in the tree of objects it contains. But you don't care about what's inside. You just care about the interface.
Exactly. Although nothing prevents you to go crazy spreading the generic vomit all over the place. It does not mean that you have to. A trait per generic vomit could be an option here.
8:50 Yes, this is ugly and I would love it when Rust trait bounds would propagate automatically, but until that is implemented it is actually possible to use a mix of super traits and generic trait implementation to basically define trait aliase.
That way the bounds wouldn't have to be copied and changes would be much easier to do.
So yes it is ugly and it would be nice if Rust had better support for these cases. But there are solutions!
What about macros? The pattern itself seems simple enough that you would only need a few parameters for the identifiers. Or am I missing something
@@harrytsang1501 Normal macros-by-example don't really work with trait bounds.
At least not well.
There is just no token to express trait bounds.
You can just use a trait tag to avoid all of that copying.
in c++ there's a dirty trick to 'solve' the template constraint thing: make an abstract interface and then check if the type you require derives from that interface. It doesn't have to make virtual calls, since you use the derived type as is, and it's sort of simple to migrate from dynamic dispatch to static. And since you can combine interfaces, checking if your type corresponds with the exact behavior you need is trivial: *template requires std::derived_from*, or even *template*
i see elden ring, i like comment (even if i didn't read it)
struct definitions should (almost) NEVER contain bounds on generics. Trait bounds define behavior, where a struct is a definition of memory layout. The moment you put a bound on a struct... you are obligated to include it on every impl block. impls define behaviors, not layout. Behaviors are defined by their bounds. I say "almost" because some marker traits (I believe just "Sized") makes sense to include on a struct, because Size is closely related to layout rather than behavior. Somewhere there is a github issue of David Tolnay arguing to include a lint in clippy for this exact thing. And tbh, that lint would have saved me soooo much time when first learning rust.
You might need bounds for the contents of your struct to even be defined. Something like
struct Foo {
bar: I::Item,
}
I see a lot of people saying "you can simplify it with a supertrait that combines all the others"... There's yet another solution too, why not just put all the possible drivers in an enum? Sure, there'll be one extra branch compared to the completely template solution, but:
* it's easily extensible
* you can use a crate (I forget the name but I know it exists) to automatically implement a trait for an enum where all the variants implement the trait too, so you can just use it like a driver with no source code changes
* if driver doesn't change at runtime, all the branches will be predicted anyway, the overhead will be minimal
And you can wrap up all the trait bounds required just at the enum definition!
This is the comment I was looking for! "Dynamic" dispatch doesn't have to be a magic abstraction like a trait object, you can just make a mini abstraction to solve your usecase without losing most of the benefits of not using runtime type objects.
I interface with a large corporation that implemented their services in Go, using Rust on my end. I can say that the holes in their Go implementation are glaring. Don't use Go for an REST API type thing. There are all kinds of logic errors that Rust would've prevented
apparently, this is just a skill issue
its so interesting to me that people think "I want this complex ass thing to be perfect at compile time" and then do 1) the most naive solution possible, 2) spend no time making it better, then 3) revert everything and praise the original design. Happy for the developer that their codebase is better, but seems like they didn't even know what they wanted. Nonetheless, playing with rust types is fun so I don't blame the naive/intuitive refactoring approach.
I'd argue that sharing a transaction is just hard regardless of the language. Especially when it involves some kind of distributed commit bollocks that i'm sure works fine except when it doesn't (which, spoiler alert, is exactly the cases where you care about it in my experience). In the end, having to use multiple transactions in a code base (or module depending on your architectural bent) seems like a huge smell to me. Having said that just like all code smells, that doesn't mean it's not actually necessary.
Also, I haven't dug around the III-IV code enough but I wonder how much of this is just down to things being included that shouldn't be the database driver's problem. Not that i'm ragging on the guy if that is the issue... keeping a design clean like that is a herculean task. Also, i'd probably set the general rule to be using dynamic dispatch and save static dispatch for places where it really matters which i'd argue it generally doesn't if your context is Web API endpoints talking to a database. You'll spend longer on protocol management and Db IO than you ever will on dynamic dispatch. (which is pretty much what you said)
In server code to avoid lifetimes just wrap your stuff in Arc. Or any other smart pointer of your choice. In IO bound context like web it would not matter and you have no silly lifetimes.
Prime is saying the same shit about the camel, previously said about rust! He is just a JavaScript technician that works for Netfix!
I have no experience with rust, but could you not have an interface (or 'trait'?) that was declared to extend those other traits? seems like you could and just declare that trait instead of having to list every trait it 'extends' in every function that uses it. Even if it's a generic. I don't get it
That would be the JEE way of doing it and everyone knows thats bad. Especially people who dont write JEE... :p
100% true
18:30 successful inlining increases execution speed at the expense of a larger binary. Respectfully, the author of the article got it backwards.
Often inlining will be for a single use, which results in sightly less overall code (a few bytes for the call and function prologue), and the real value is when the inlining lets the optimizer take advantage of local conditions to simplify the inline code, so it's not always an increase in size.
But you're generally correct!
This isn't inherently an issue about static dispatch at all.
However, doing static dispatch right AND ergonomic is pretty hard in some circumstances like this, and to have a truly ergonomic solution you need to either dive into the rustnomicon and carefully write some unsafe bits, AND/OR get to writing some macros.
there is also a way to handle bits on a per method basis, thus you get less of the inconveniences
Why not create new super Traits like `Trait A: Clone + Send + Sync {}` You would still have to specify it but surely writing where `B: A` is less atrocious. Am I missing a downside of such an approach?
It could be an issue if functions do not share trait bounds aka function 1 needs traits A B C but function 2 needs A B D. Composition can still help, at the very least in the example code & what we can infer their codebase to look like with the clauses "repeated". A different issue (that probably isn't what the developer was facing) could be that you may want additional/different function-specific constraints per function for preventing misuse of the function, though in rust this is typically more of a stylistic choice than a need, since this prevention of code smell is better solved using the compiler as a way to get the naive solution and then using lints or reviewing your code to optimise later.
It’s the same with templates in C++. Using dynamic dispatch (virtual methods through interfaces) is often easier, cleaner and compiles faster even if it has a runtime cost. Don’t invoke a dynamic dispatch method to write a single byte, instead pass it a whole buffer and the vtbl-jump penalty becomes negligible.
Totally agree, this issue is more related to bad code design rather than Rust limitations
Yes, it is bigger not smaller (but faster in runtime). However in C++ you can use an interface (just for the type, no virtual methods) and then set a requirement in the derived template. All static checked and super easy to implement with respect to Rust.
I have seen in the comments way to massively simplify the issue, but rust is also working on implied trait bounds which would eliminate the need for most where clauses in this code
yeah Rust could really use some more inference tools where you can just type a keyword and just tell the compiler to figure it out. Even if it was just a relative type definition, that would be good. Like "typeof Argument1(f)" that way if you ever changed the type of f's argument, it would propagate.
My biggest problem with Go is the error handling. If it had pattern matching like Rust I'd probably use it more.
Due to your shilling, I'm going to have to check out OCaml.
Also in most cases the correct answer would be to use an enum if what you are developing is not a library. Even if it is a library there is a real benefit to put all the variants you define yourself into an enum and having an `Other` variant which contains the `dyn`.
That's why you don't pass the DI struct around directly; you define a trait, turn all the parameters into associated types, implement it, and then pass down an `impl FooContext`. This is symmetrical to how you'd define DI in C# or Java, only without dynamic dispatch.
Its interesting how "lets see" is your "I'm speaking further than I've read, I need to go back to catch up"
yep
That driver definition makes OO look good
this is what OO would look like if it didn't have composition or types (aka not OO). The comparison doesn't work! I know you were probably joking, but I wanted to agree before thinking about it lol
@@mikkelens yes I was joking
i use cpp if I need maximum performance, rust if I need high performance or I want to use rust, and c# if I don’t care about performance at all. But inevitably I always regret using c# because i want more performance later down the road
Is there any world where the speedup from static dispatch to relational database calls is relevant when compared to the time spent in IO for those calls?
Asking for a friend…
if you would go for the static dispatch , and utilize a bunch of super traits , it is better to create macro to avoid the disaster of having to write all implementations. If u wanted performance then let the compiler do the work for you… at the expense of longer compile times, but you can opt to expand those macro later on if u wanted to…
Untyped null is now considered a billion dollar mistake, in the future type metaprogramming will also be considered a billion dollar mistake.
Leaking the where block to the REST layer is the bigger problem.
The feelings about rust have changed so much on this channel
Prime is a pretty dramatic guy. He couldn't stop shilling Rust when it was new to him and now he can't stop shilling the new shiny thing he can't see negatively. Now that he's over Rust as the One True Language he can view it more realistically, but goes a bit further and views it negatively to contrast it with his feelings for other languages. To me what makes Rust shine through his perspective only is how much he has to admit it's still super good despite it maybe not being deity-status, contrasted with how he doesn't really talk about zig that much anymore since trying/"moving onto" OCaml
Lol, I just did something similar this week resulting in 2x end-to-end speedup
...It's safe because it makes it impossible to try and do things that haven't already been done... the framework achieves UNLIMITED SAFETY despite being written entirely in gotos! It's perfect safety record is compounded by this decision as most developers refuse to even CONSIDER working on systems that use just one goto that's not next to a token called "while" or "void*". Arbitrary semantics implemented as macros are tolerated giving the framework infinite extensibility that's NEVER USED, further contributing to its overall safety.
There are some good comments at the end of the reviewed article I think. Would be curious about your thoughts on those comments!
Tom is genius
Tom is genius
Bruh… this is an overplayed joke already
Isn't it: "Tom's a genius"?
Ultimately it seems like the only reason they need to change the database is so that they can use sqlite in tests, apart from that, using just Postgresql is fine. I hate to be the TDD guy, but this is the issue with testing after writing the code, you can build something that is so tightly coupled to a system that shouldn't be in tests that it turns into this when you actually get around to testing.
If this was built up with tests using sqlite and units using postgresql, piece by piece, it could have built up a must less leaky abstraction. It still could have become this, but the issues would have shown up sooner where it would have been easier to fix.
I wrote something like this like 2 weeks ago but ended up refactoring it just fine.
But I do agree there's no clear way to refactor the number of generics in a thing both up and down in rust. That's super annoying and I hope something like trait bound alias can land.
Before that some kind of macro can probably help in some situations.
What do you mean by "trait bound alias"? Like the "type" keyword? I guess you can't use type for traits like you can for structs/enums (since they aren't types in the same way), but you can use trait constraints to bundle them together, and it feels pretty similar: "trait C: A + B { }"
Once put two days of work into setting up icinga... took me a day to get it running stable in docker (first time)... then i saw the configuration and dropped it almost instantly xD
Sunken cost fallacy my ass :)
Isn't that where associated types come in!?
Just rewrite it in C++ lol
If you are trying to make something this complex, sharing transactions, extended traits used on several layers... Static dispatch is simply not the right choice. Don't make it sound like static dispatch its the only right way. Dynamic dispatch is actually the most common way how every other language does this. It is perfectly fine. Rust just gives you the rare option to create a static source from generic functions in case it is possible.
That absolutes line is an absolute
Tom would've figured that shit out! Did u know that Tom is a genius?
6:09 Is there no form of type aliasing in Rust?
You missed the obvious: That phrase is an absolute. Ergo, anyone using is outing themself as a Sith.
22:08 Raw JS is super useful!!
The way he talks is Bill Burresque
“I tried a day of java and java sucks but javascript is great”. That hurts to hear even as a strawman.
Massive skill issue.
the syntax `trait Foo = Send + Sync + 'static;` is nightly, but you can do
trait Foo: Send + Sync + 'static {}trait Foo: Send + Sync + 'static {}
impl DbDriver for T {}
trait TxDriver: Tx + Send + Sync + 'static {}
impl
As a JS developer (primarily), yes, it does suck!
Not sure why the author thinks modern C++ is deranged but this is not. Rust is usually great, but I’ve never had to write monstrosities like that in C++.
8:51 Simply fix the ™ sign in the browser's developer tools, problem solved.
> `Arc`
Am I the only one who uses feature flags and conditional compilation to do mocking in Rust? I detest having to change the way my code is written just to accommodate mocks
5:50
Not familiar with Rust but this kind of repetition could easily be solved with a C Macro :)
trait composition (written like "trait C: A + C { }") is like trait 101 and its pretty interesting that the author doesn't mention it or isn't familiar with it
What if I am also a child?
Only a sith deals in in absolutes.
This is the worst thing about Rust in my experience, its a leaky language, in the sense that implementation details of your types and modules easily explode into a mess across the entire program.
big skill issue right there, use associated types
you can also constrain the method to the trait required like hashmap does (you don't need a bunch of traits to define all its behavior, just on the methods you use)
@@ThePrimeTimeagen Very good point! Though that being said, if your trait has a method that takes a generic parameter, it won't be considered object-safe anymore (so, no Arc).
I'd rather prefer something like this:
trait Foo {}
trait Bar {}
trait Baz {}
trait Qux {}
trait DoesItAll: Foo + Bar + Baz + Qux {}
impl DoesItAll for T where T: Foo + Bar + Baz + Qux {}
Then you can just do a "where T: DoesItAll" on your generics and boom, you've reduced the where-clause-boilerploooting.
Rust does actually provide a lot of ways to reduce complexity, but unfortunately those are hard to come by when you've already coded yourself into a corner.
@@azratosh
Your method is good and it has its place, for example when you do have unsafe code and you need to uphold some invariants no matter what. But for that use case it might be an overkill.
About the type safety of constraining methods to traits. Rust will not let you exhibit behavior (call a method) if traits are not satisfied (for example, call `.clone()` if trait is not `Clone`, so it is absolutely type safe to, for example, have a struct that should be `Clone` but to not restrict it's generic bounds at the definition points, but rather implement `Clone` where all generics are `Clone`. Then, at the point where you need it to be `Clone`, constrain. That will save a lot of boilerplate.
@@ddystopia8091 Oh yeah, totally. I would never constrain the T on a Struct directly (unless really necessary); only on the impl blocks. Keeps everything tidy and modular.
Why even optimize dynamic dispatch when you are doing network call with it
Python rewrite, it'll take you 10 minutes and there's no compilation. Let's go!
Are you alright Prime my dude?
You look a bit tired in the video.
Are you taking proper time to rest?
i am ! i have reduced my streaming 1 day less a week.
the thing that is hurting me right now is teaching courses for front end masters
Make a playlist for prime reacts
...?
look at the playlist this is on!!! its on prime reacts!
@@ThePrimeTimeagenanotha one
Couldn't the duplication not handled with a Macro?
25:19 apparently not
@@bary450 Oops, not sure how I missed that part. Thanks for pointing it out.
D's openmethods = problem solved 😎
Forth. Thanks for the recommendationt to practing coding typing! super helpful!
if you have to paste a assload of where statement 44 times why would you not include it in either a build script or a macro of some kind?
`trait Db: Send + Sync {`
This article again?
My hot take is there's no such thing as a zero cost abstraction
The whole reason to use C++ is that it offers many zero-cost abstractions.
I'll get you one better: there isn't anything that is universally true/perfect for everything, it's just a matter of tradeoff scale along with what tradeoffs you care about and what tradeoffs you don't.
also @agcwall misunderstanding the original comment and acting like c++ is uniquely offering Perfect Zero-Cost Abstractions in this regard is pretty funny considering the subject matter
I do appreciate rust on a personal level, I am not a rust shill, but please don't take this as a bad faith criticism. this is a genuine comment/question:
I am relatively new to your channel, and you seem like someone who tries to see things in a logical way, but when it comes to rust, it seems like you want to see it fail. I'm curious to understand why that would be the case.
There is a solution! Just a trait wrapper! Really!
Example in my comment below (hope TH-cam didn't ghost-remove it)
pub struct SimpleDriver {
driver: X,
}
impl SimpleDriver {
pub async fn simple_run(&self, cmd_params: CommandParams) -> Result {
self.driver.handle(cmd_params).await
}
}
// part2
#[async_trait]
pub trait DriverTrait {
async fn handle(&self, cmd_params: CommandParams) -> Result;
}
#[async_trait]
impl DriverTrait for Driver
where
F: Fn(CommandParams) -> T + Send + Sync + 'static,
T: Future + Send + Sync + 'static,
{
async fn handle(&self, cmd_params: CommandParams) -> Result {
let result = (self.inner)(cmd_params).await;
result
}
}
too much videos, quality is meeeh. Was nice to follow you for a time. You gave some good advice!
TBW, I still think code generation is the way.
But at compile step, where the generated code is the output of a library then then you import from somewhere else.
My eyes
OCaml shilling is NOT annoying.
@9:30 This where could be shorten and DRY-ed by supertraits...
Guy don't know Rust well and trying to implement concepts from another languge.
I saw the same with guy who was c++ programer and make presentation about how he tried to port his app to rust.
When he wrote that sql driver spoils rest endpoints that was clear that design is poor and/or cargculted from other langage
7:53, can't do macros for that?
You don't like Rust anymore Prime?
this sounds like a case for a macro
Elm is better than TypeScript it is a lot more ergonomic
yes, All of us are :(
twenty ninth?
Edit: Fixed grammer errors
So much time spent on Unreadable types when you could just manually write a codegen build step in less time. C doesn't have this problem
Are there good lanugages with async/concurrency without red blue function distinctions? (other than go)
whats red blue function distinctions?
@@RenderingUser JS and Rust have regular and async functions. You can call regular functions from async but not vice versa. Distinction with weird asymmetry.
@@gritcrit4385 I see. But is that much of a problem tho?
@@gritcrit4385you *can* call sync from async, but you need to be careful it won't block, and you *can* call async from sync, but not then get the result without higher level coordination.
I believe zig has that ability (when the async stuff is actually included in a release lol)
coroutines in lua are made from normal functions w/ the ability to yield from any function in the coroutine (anywhere in the stack), not sure if that counts.
Should have used a macro to automate that where block.
Unneeded macro! Just do trait composition/self-constraints: "trait C: A + B { }"
11:46 Just go back to dyn, not GC!
This is one the issues I have with adding any constraint, it just infects every piece of code that the type/trait is involved with.
Just make type aliases
NaN?
Nani?
NaM?
Nun exception: God is not found
Please more OcaML shilling
Second?
First to Second?
Красава супер видео
Stupid youtube bot deleted my comment because of a link
Here's rust playground gist: 4ce0f67a1cc096b217dea252465059e3
The point is that you can specify all you trait bounds in a single trait with associated types. And leaking abstractions is not a "Rust issue", it's a skill issue
Child exploitation is wrong absolutely therefore I'm a sith and proud of it. The "dark side" of the force allows one to shine more brightly imo. Good vid PrimeTime. Ethical appeal 11/10.
From the vid I see that setting up domains and policies are the real issue where Go and others are merely a way to develop (like DB transactional interfaces) without getting involved with policy definition which is a double edged sword; laziness being the edge facing the wielder. Ignorance regarding Rust policies is valid but understanding one's own code and policies regarding interfaces would make it far easier to query the community and documentation relationally, so...
Not advocating for or against Rust and others but many arguments/bullets seem like a one and done throwing in of the towel many times regarding Rust development. Tech officers and testers are generally going at languages with a bit more openness and curiosity as well as a deeper understanding of things of such a magnitude require the adequate level of consideration opposed to a quick "deep dive" considering something as monumental as a total migration. Looking back it feels as though I'm regurgitating what you've stated and wonder if those "devs" are listening to your advice because you sound familiar to a CTO. Good stuff.
First?
First to First?