Rust Tip - Into String as Function Arguments

แชร์
ฝัง
  • เผยแพร่เมื่อ 23 พ.ย. 2024

ความคิดเห็น • 47

  • @JeffHanke
    @JeffHanke 2 ปีที่แล้ว +68

    One thing to be careful with when accepting trait arguments is that rust will duplicate the entire function when it specializes it for each concrete type. If you care about binary size, your function is big, and you're going to call it with lots of different types, you probably want to factor out most of the logic into a helper function and just do the .into() (or other conversion) call in the main function.

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +18

      Yes, binary size is a tradeoff when impl trait as args in general.
      I have not done the math, but for low argument functions, I think the tradeoff is worthwhile. But this is a fair point to consider if binary size is critical.

    • @dragonmax2000
      @dragonmax2000 2 ปีที่แล้ว +1

      @@JeremyChone Is there is a different way to do this without such expensive operation? Also, it is not clear whether this can be a general approach to all things that pass string over?

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +11

      Putting the binary size cost aside as I think this can be negligible in most cases.
      From an allocation point of view, assuming your function needs the full String anyway, this impl Into< String> is cheaper, as it allows the caller to move the full value if it can.
      So, rather to do a .to_string() no matter what (if the arg is &str), or require String no matter what (with arg: String), the impl Into allows the caller to pass the full value if it can to avoid unnecessary allocation.
      Now, if your function does not need the ownership of the String to do its work, then it's probably better to accept &str.
      Did I answer your question?

    • @dragonmax2000
      @dragonmax2000 2 ปีที่แล้ว +3

      @@JeremyChone Yes, you did. Thank you!

    • @aelarion827
      @aelarion827 ปีที่แล้ว +6

      @@dragonmax2000 just to add to Jeremy's answer, you can use basically the same implementation in the video with `impl AsRef` if you don't need to own the data.
      This is common with functions taking an argument that implements AsRef, where you don't necessarily need to own a PathBuf, but instead just need to reference something "path-like" (for example, to read or write to a file). This would accept strings, string slices, PathBuf, Path, etc.

  • @marcelocerri3779
    @marcelocerri3779 ปีที่แล้ว +8

    It's important to note that you need to be very careful when specifying traits with impl arguments because that will make the trait not object safe.

  • @wicpar
    @wicpar 2 ปีที่แล้ว +11

    It's important to know that the Into will copy the string if it is not already owned. If you can afford a lifetime it's better to use &str and work with references.
    In the function call using & will dereference any type to &str that implements it.

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +7

      Yes, if you pass a &str or &string the into() will create a new string, if you pass a String it will not.
      So, in a way, the caller has the control of new allocation or not.

    • @DuckTVDirector
      @DuckTVDirector ปีที่แล้ว +1

      `Into` may be less efficient because it requires converting the value into a `String` before processing it, which may involve allocating memory on the heap. However, it may be more convenient to use because it allows you to pass any type that can be converted into a `String`. I heard using `AsRef` are more efficient because it can work with a `&str` slice directly, without requiring any additional memory allocation.

    • @JeremyChone
      @JeremyChone  ปีที่แล้ว +1

      There is no extra convertion when the argument is a String. So, no extra work.
      The only downside is the binary size as the function get duplicated for each usage. See description for bin size mitigation.

    • @dmitriidemenev5258
      @dmitriidemenev5258 ปีที่แล้ว

      This issue is addressed by `maybe-owned-trait` crate that I published a while ago.

  • @embeddedbastler6406
    @embeddedbastler6406 2 ปีที่แล้ว +1

    I think the equivalent thing in C++ are the universal references, where the function parameter might be an l-value reference (that would need to be copied into an owned type) or a r-value reference (that can directly be moved into an other owned type) depending of the actual usage.
    For example the equivalent C++ code for the code in the video is:
    template
    auto do_stuff(T&& val) {
    std::string owned_val = val;
    }

  • @sanctuary_of_soul
    @sanctuary_of_soul ปีที่แล้ว

    NICE! Although i had to rewatch it few times to grasp the catch. Thanks

  • @fabricehategekimana5350
    @fabricehategekimana5350 3 หลายเดือนก่อน

    Really powerful ! Thanks a lot !

  • @alexzander__6334
    @alexzander__6334 2 ปีที่แล้ว +6

    what is the difference between `Into` and `AsRef`? both work the same in your example

    • @wicpar
      @wicpar 2 ปีที่แล้ว +2

      AsRef cen only get a reference &'_ str whereas Into< String> allows you to get an owned String

    • @adrianjdelgado
      @adrianjdelgado 2 ปีที่แล้ว +11

      Into does unnecesary heap allocations in the cases you only need a &str. Use Into only when you need an owned String.

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +11

      Correct, this is an important point. This technique works well when your function body need the full String of the argument. In short it gives control to the caller if the string should be moved or cloned.
      Otherwise, use &str or AsRef, if you just need the reference.

  • @syaPK
    @syaPK 2 ปีที่แล้ว +1

    i love. much more like this shorts

  • @elanticrypt0
    @elanticrypt0 ปีที่แล้ว

    Thank you Jeremy.

  • @piotradamczyk3932
    @piotradamczyk3932 ปีที่แล้ว

    Thank you, it seems like overloading from C++

  • @learnityourself
    @learnityourself 2 ปีที่แล้ว +1

    Excellent as always.

  • @moquette31
    @moquette31 ปีที่แล้ว

    How about no_std context and use of heapless::String ? Is it the same ?

    • @JeremyChone
      @JeremyChone  ปีที่แล้ว

      Ha, I do not know the answer to this one. If anybody wants to chime in.

  • @crazyingenieur3277
    @crazyingenieur3277 7 หลายเดือนก่อน

    How about `impl ToString` or `&impl ToString` as a function/method argument?

    • @JeremyChone
      @JeremyChone  7 หลายเดือนก่อน

      You would actually need to `impl Display`, and then you can call `to_string()`.
      That approach works too, and in fact, it can take more types, but the downside is that this will require calling `to_string()`, which will always do a new string allocation.
      `impl Into` will not generate a new string on `.into()` if a String is passed.

    • @crazyingenieur3277
      @crazyingenieur3277 7 หลายเดือนก่อน +1

      @@JeremyChone Thank you for the quick response.

  • @LinuxForLife
    @LinuxForLife ปีที่แล้ว

    Nice! I will sleep tonight less idiot! 😁

  • @katopz
    @katopz 2 ปีที่แล้ว

    I think we can just do_stuff(val:String) // no Into needed?

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +2

      Yes, you can, but as shown in the video, the callers have to do the .to_string() if they have references or string slice. So by accepting into…, the caller pass what they have as long as it implements this into.
      If you want the Into trait on one of your type, make sure to impl From (and not the into)

  •  ปีที่แล้ว

    I generally discourage this pattern due to code multiplication. Passing "...".into() expresses the intention better and avoids multiple unnecessary code copies.
    Only use this where it's extremely necessary.

  • @ДмитрийБондаренко-ф7ь
    @ДмитрийБондаренко-ф7ь ปีที่แล้ว +2

    why not do so?
    fn do_stuff(val: T){}
    You variant is syntactic sugar?

    • @JeremyChone
      @JeremyChone  ปีที่แล้ว +2

      Yes, it’s the same. Both are good.

  • @natec1490
    @natec1490 ปีที่แล้ว

    Why not &str

    • @JeremyChone
      @JeremyChone  ปีที่แล้ว +3

      First, if the function body does not need to own String, yes, &str should be used.
      Now, if the function body must own the String, then using impl Into has the following advantages:
      - When &str or &String is passed as an argument, new string allocation occurs on .into()
      - When String is passed, no new allocation occurs on .into(). The String is moved to the function.
      This way, the caller does not have to call .to_string() when it wants to clone the String, but can pass the full value when it is ok to be moved.

  • @PandaNuker
    @PandaNuker 2 ปีที่แล้ว

    oui

  • @snesmocha
    @snesmocha 2 ปีที่แล้ว +3

    ah yes, “hello”.to_string()
    What the hell
    This literally looks like chaos… why does a string need to be a reference type??

    • @christopher8641
      @christopher8641 2 ปีที่แล้ว +7

      it doesnt have to be, thats just one way to do it. Sometimes having the tools to precisely describe your intent to the compiler is very useful. Can you not imagine a situation where you want to reference a string?

    • @christopher8641
      @christopher8641 2 ปีที่แล้ว +2

      Im going to add on to this because I'm a few months into Rust and about a year into self taught programming in general. Teaching builds comprehension
      Basically, T (A generic symbol to represent a type), &T, and &mut T are all different types. If I own a car, and you want to take a picture of it (as a reference), you don't own my car. If you want to store your reference, it will obviously take much less space than storing the owned version that I have (the physical car). If the storage company is managed by the compiler, saying "I'm dropping off my car later" just isn't enough information. If I decide to paint my car, it invalidates the picture you have. We can get around this by writing a contract that says "Your picture will match my car until the end of this contract's lifetime".
      There is still a ton of information that can be done with your reference, you can even perform some similar actions on it as the physical car. You could say get_car_model and get the same result.
      The better terms of the analogy would be if your reference is the address at which the car is stored, and the contract being a guarantee the identical car will be stored there until its lifetime expires.
      Not having the option to define intent as "reference to T" forces assumptions...Assumptions suck if you want a safe and efficient runtime. That is a string literal needs to be a reference type, with a static lifetime [&'static str]. That is my understanding of it at least

    • @charetjc
      @charetjc 2 ปีที่แล้ว +3

      Because a string literal is copied by the compiler into (read-only?) data memory of the executable, and therefore is best represented as a reference, unless you want to allocate memory (which you control/own) and copy that literal over so you can now modify the characters in the copy (which .to_string() does).
      I suppose order might seem like chaos to those ignorant of the choices made for them in other languages.

  • @realsong-fake
    @realsong-fake 2 ปีที่แล้ว +1

    This is just wrong. What you did can be consider one of the worst example show case `From/Into` traits.

    • @JeremyChone
      @JeremyChone  2 ปีที่แล้ว +7

      Can you elaborate? “impl Into” as function argument is a well known pattern in Rust. Nothing new here.
      Btw, this was not about show casing ‘From/To’ traits. It was about show casing the value of impl Intoas function argument.