当前位置:网站首页>[rust submission] review impl trail and dyn trail in rust

[rust submission] review impl trail and dyn trail in rust

2022-06-25 03:52:00 51CTO

PrivateRookie -- author


origin

Everything must be changed from the end of the year to the special period , I read a few poems at home , I suddenly want to write a poem that can be browsed and recited TUI Procedure starts . I chose  Cursive  This Rust TUI library . There is such a function in the implementation , It will return a component according to different parameters ( Such as Button, TextView etc. ). stay  ​​Cursive​​​  in , Each component implements  ​​View​​  This trait, Initially, this function will only return a certain component , So the function signature can be written like this

      
      
fn some_fn(param: SomeType) -> Button
  • 1.

As development progresses , This function needs to return Button, TextView One of the components such as , I subconsciously wrote something like this

      
      
fn some_fn(param1: i32, param2: i32) -> impl View { if param1 > param2 { // do something... return Button {}; } else { // do something... return TextView {}; }}
  • 1.

unfortunately Rust The compiler always smacks in the face , Rust The compiler reports an error as follows

      
      
--> src\main.rs:19:16
|
13 | fn some_fn(param1: i32, param2: i32) -> impl View {
| --------- expected because this return type...
...
16 | return Button {};
| --------- ...is found to be `Button` here
...
19 | return TextView {};
| ^^^^^^^^^^^ expected struct `Button`, found struct `TextView`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

From the compiler error information, we can see that the return value of the function is  ​​impl View​​​  But it comes from  ​​if​​​  The branch inference return value type is  ​​Button​​​  No longer accept  ​​else​​​  Branch returns  ​​TextView​​​. This is related to Rust requirement  ​​if else​​  The return value types of the two branches are the same . Can you make the function return multiple types ? Rust The reason why a function cannot return multiple types is because Rust In need in The compile time determines the memory size occupied by the return value , Obviously, different types of return values have different memory sizes . In that case , Pack the return value , Returns a fat pointer , In this way, the size of our return value can be determined , Maybe it will be OK . Try changing the function to the following form :

      
      
fn some_fn(param1: i32, param2: i32) -> Box < View > { if param1 > param2 { // do something... return Box::new(Button {}); } else { // do something... return Box::new(TextView {}); }}
  • 1.

Now the code has been compiled , But if Rust 2018, You will find that the compiler will throw a warning :

      
      
warning: trait objects without an explicit `dyn` are deprecated
--> src\main.rs:13:45
|
13 | fn some_fn(param1: i32, param2: i32) -> Box < View > {
| ^^^^ help: use `dyn`: `dyn View`
|
= note: `#[warn(bare_trait_objects)]` on by default
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

The compiler tells us to use trait object Do not use  ​​dyn​​​  The form of has been abandoned , And it also reminds us to put  ​​Box<View>​​​  Change to  ​​Box<dyn View>​​, Follow the compiler's prompts to modify the code , Now the code no warning, no error, perfect .

but  ​​impl Trait​​​  and  ​​Box<dyn Trait>​​​  Is there any difference except that multiple return value types are allowed ? ​​trait object​​​  What is it again? ? Why?  ​​Box<Trait>​​​  The return value of form will be discarded and new  ​​dyn​​  Key words? ?

Buried pit

​impl Trait​​​  and  ​​dyn Trait​​  stay Rust They are called static distribution and dynamic distribution respectively . In the first edition of Rust Book Explain distribution in this way (dispatch)

When code involves polymorphism, there needs to be a mechanism to determine which specific version is actually run. This is called ‘dispatch’. There are two major forms of dispatch: static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called ‘trait objects’.

That is, when the code involves polymorphism , Some mechanism is needed to determine the actual call type . Rust Of Trait It can be seen as some collection with pass through feature types , Take the above code for example , When we write code, we don't care about specific types , However, it must be determined at compile time or run time  ​​Button​​​  still  ​​TextView​​. Static distribution , Just like static typed languages " static state " The word , The specific call type is determined at compile time . Rust The compiler will use singleton (Monomorphization) Expand generic functions .

hypothesis  ​​Foo​​​  and  ​​Bar​​​  It's all done  ​​Noop​​  characteristic , Rust Will function

      
      
fn x(...) -> impl Noop
  • 1.

Begin to

      
      
fn x_for_foo(...) -> Foofn x_for_bar(...) -> Bar
  • 1.

( For principle explanation only , There is no guarantee that the compilation will expand the function name in this way ).

By monometalization , The compiler eliminates generics , And no performance loss , This is also Rust The form advocated , The disadvantage is that too many expansions may cause the volume of the secondary system file generated by compilation to be too large , You may need to refactor the code .

Static distribution has high performance , But at the beginning of the article, another shortcoming is also reflected , That is, functions cannot return multiple types , therefore Rust Also support passing trait object Realize dynamic distribution . since Trait Is a collection of types with certain characteristics , Then we can put Trait Also as a certain type , But it is " In the abstract ", It's like OOP Abstract class or base class in , Cannot instantiate directly .

Rust Of trait object Used with c++ Allied  ​​vtable​​​  Realization , trait object contain 1 Points to the actual type  ​​data​​  The pointer , And an implementation that points to the actual type trait Functional vtable, This enables dynamic distribution . A more detailed introduction can be found in

Exploring Dynamic Dispatch in Rustalschwalm.com


notice . since trait object The size can be determined during implementation , Then why not  ​​fn x() -> Trait​​​  In the form of ? although trait object The size can be determined in the implementation , But logically , because Trait Represents a collection of types , Its size cannot be determined . allow  ​​fn x() -> Trait​​​  It will lead to semantic disharmony . that  ​​fn x() -> &Trait​​  Well ? Certainly. ! However, in this scenario, all references are created in the function and then return the value , Obviously, the life cycle needs to be added :

      
      
fn some_fn(param1: i32, param2: i32) -> &'static View { if param1 > param2 { // do something... return &Button {}; } else { // do something... return &TextView {}; }}
  • 1.

I don't like to add additional lifecycle descriptions , You must be the same . So we can use the  ​​Box​​​  Smart pointers avoid annoying life cycle descriptions . thus  ​​Box<Trait>​​​  At last . So here comes the question , Why does the compiler prompt  ​​Box<Trait>​​​  Will be abandoned , Specially introduced  ​​dyn​​  Key words? ? The answer can be in  RFC-2113  Find .

RFC-2113 The introduction of  ​​dyn​​​  Why , That is, semantic fuzziness , It's confusing , The reason is that there is no  ​​dyn​​  Give Way Trait and trait objects It looks exactly the same , RFC List 3 An example shows .

First example , Add the code you see below , Do you know what the author wants to do ?

      
      
impl SomeTrait for AnotherTrait impl < T > SomeTrait for T where T: Another
  • 1.

Do you understand ? To tell the truth, I can't understand : ) PASS

Second example , ​​impl MyTrait {}​​  Is the correct grammar , But it makes people think that this will be in Trait Add default implementation on , Extension method or other Trait Some of their own operations . In fact, this is trait object Add method on .

As explained in the following code , Trait The correct way to define the default implementation is to define Trait When you specify , Instead of  ​​impl Trait {}​​  In the block .

      
      
trait Foo { fn default_impl( &self) { println!("correct impl!"); }}impl Foo { fn trait_object() { println!("trait object impl"); }}struct Bar {}impl Foo for Bar {}fn main() { let b = Bar{}; b.default_impl(); // b.trait_object(); Foo::trait_object();}
  • 1.

​Bar​​​  In the realization of  ​​Foo​​​  You can go through  ​​b.default_impl​​​  call , No additional implementation is required , but  ​​b.trait_object​​​  No way. , because  ​​trait_object​​​  The method is  ​​Foo​​  Of trait object The method on the .

If it is Rust 2018 The compiler should also display a warning , Tell us that we should use  ​​impl dyn Foo {}​

The third example is based on function type and function trait Contrast , The only difference between the two is whether the initial letter is capitalized (Fn Representative function trait object, fn Is the function type ), It is inevitable to confuse the two .

More detailed instructions can be moved

RFC-2113github.com


.

summary

​impl trait​​​  and  ​​dyn trait​​​  The difference is that static distribution is different from dynamic distribution , Static distribution performance good , However, extensive use may cause binary file expansion ; Dynamic distribution to trait object The concept of is realized through virtual tables , There will be some runtime overhead . Because of trait object And Trait Without introducing  ​​dyn​​​  It often leads to semantic confusion , therefore Rust Specially introduce  ​​dyn​​  keyword , stay Rust 2018 Has been stabilized in .

quote

The following are the references for this article

impl Trait for returning complex types with easedoc.rust-lang.org

impl trait Community Tracking github.com

rust-lang/rfcsgithub.com【Rust contribute 】 Smooth it out Rust Medium impl Trait and dyn Trait_ compiler Traits and Trait Objects in Rustjoshleeb.comDynamic vs. Static Dispatchlukasatkinson.deExploring Dynamic Dispatch in Rustalschwalm.com



PS: The picture above shows Lupu Bridge , My favorite bridge in Shanghai , Not one of them. ~


原网站

版权声明
本文为[51CTO]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206250332316110.html