Search code examples
rustpolymorphismparameter-passing

Polymorphism in Rust. AsRef vs Deref


I want to create a function that can take as an argument String, &str, Rc<String>, Arc<String>, etc. String is just an example, the parameter can be of any type. I can do this using AsRef:

fn is_empty_as_ref<T: AsRef<str>>(arg1: T) -> bool {
  arg1.as_ref().is_empty()
}

Or Deref:

fn is_empty_deref<T: Deref<Target = str>>(arg1: T) -> bool {
  arg1.is_empty()
}

In my playground, both of these approaches work well. So I have 3 questions:

  1. In general, without any specific case, what trait should I use for such polymorphism?
  2. In which cases should I use one instead of the other?
  3. Are there other commonly used traits for that kind of polymorphism? (I don't need it, but maybe I'm missing something)

Solution

  • String is just an example, the parameter can be of any type.

    It can not, actually. Because there is no impl AsRef<T> for T. So for most types, AsRef will not let you pass in an owned value.

    In general, without any specific case, what trait should I use for such polymorphism?

    In general, none of the above, request an &T and let the caller handle the conversion. Notably if they have a Deref<Target=T> they can just call

    do_thing(&value)
    

    and deref coercion will handle the rest.

    In which cases should I use one instead of the other?

    I don't believe there's anything clear-cut. The documentation for AsRef has various notes and furthermore covers the case of Borrow: https://doc.rust-lang.org/std/convert/trait.AsRef.html

    Generally speaking, Deref is what's implemented by smart pointers, to allow accessing their pointees. "Smart pointer" is itself a somewhat fuzzy concept, usually designated by the ability to be Deref'd, and notably does not mean "transparent wrapper" since e.g. String or Vec are smart pointers.

    In the stdlib, AsRef is used to cheaply convert to broader / less restrictive types with the same underlying structure e.g. strings (of all kinds) to OsStr or Path, as a String is more restrictive than the other two, and thus any string is also a valid OsStr / Path. In an extremely limited way, AsRef acts a bit like an inheritance system.

    Finally Borrow is more specific to maps, in some ways it's a more restrictive than AsRef as it has additional requirements (specifically that the Borrow-ed reference has hashing, equality, and ordering matching the original), but it is implemented for T. So Borrow has a two-way substitutability implication, it considers that the borrowed and the borrowee are equivalent but for ownership, which is not the case of AsRef.

    Are there other commonly used traits for that kind of polymorphism?

    What kind of polymorphism? Internally specific but externally generic parameters? From/Into and TryFrom/TryInto are probably the most common.