# Better syntax for the std::convert traits

**URL:** <https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272>\
**Category:** language design\
**Created:** [July 28, 2024, 10:16pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272 "2024-07-28T22:16:44Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ardi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ardi/32/11543_2.png) [@Ardi](https://internals.rust-lang.org/u/Ardi)\
**Post date:** [July 28, 2024, 10:16pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/1 "2024-07-28T22:16:44Z")

</div>

I think that the main reason that people don't use the convert traits very often is because they're annoying to use, forcing the conversion to the caller (who shouldn't really care).

```rust
fn foo(arg: &str) {
  ...
}

fn bar<R: AsRef<str>>(arg: R) {
  let arg = arg.as_ref();
  ...
}

```

`bar` has a better API than `foo` but it's more annoying to write.

We could have some syntax that desugars to the traits like this

```rs
fn foo<T>(arg: do convert &T) // For AsRef
fn foo<T>(arg: do convert &mut T) // For AsMut
fn foo<T>(arg: do convert T) // For Into
fn foo<T>(arg: do convert Result<T, _>) // For TryInto (I'm not sure how useful this will be though)

```

This has the benefit of being easier to write, easy to refactor, not require a generic / `impl` and not require the shadowing line.

---

<div class="post-metadata">

**Author:** ![binarycat](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/binarycat/32/12210_2.png) [@binarycat](https://internals.rust-lang.org/u/binarycat)\
**Post date:** [July 28, 2024, 10:59pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/2 "2024-07-28T22:59:55Z")

</div>

> [@Ardi](#):
>
> that people don't use the convert traits very often

i see them used a lot in "high level" apis like `reqwest`, and i call `into` a fair bit in my own code.

I would say if anything needs syntactic sugar, it would be implementing single-function traits, which (at least in the case of traits that mirror other traits, like Borrow, Deref, and AsRef), function delegation will help with

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [July 28, 2024, 11:25pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/3 "2024-07-28T23:25:42Z")

</div>

> [@Ardi](#):
>
> ```rust
> fn foo(arg: &str) {
> ...
> fn bar<R: AsRef<str>>(arg: R) {
> 
> ```

`bar`'s signature has significant disadvantages over `foo`’s:

- `bar` is generic, so it has to be monomorphized for each concrete type `R` it is used with, for each calling crate. `foo` is not generic, so it only needs to be compiled once (unless inlined). Therefore, the program using `bar` likely takes longer to compile, and may be larger.

- `foo` 's parameter is a [coercion site](https://doc.rust-lang.org/reference/type-coercions.html) that can cause deref coercion for types like `&std::cell::Ref<'_, str>`, which eventually dereference to `str` but do not implement `AsRef<str>`, but `bar` does not allow this.

**We should not encourage authors to write functions generic over `AsRef` unless these disadvantages are mitigated in some way.** The place where `AsRef` is worth using today is for _elements of containers_; for example,

```rust
fn bar<R: AsRef<str>>(arg: &[R]) {

```

is flexible in a way that is important beyond ergonomics, because it can accept a `[&str]`, `[String]`, or even third-party types like [`[ArcStr]`](https://docs.rs/arcstr/1.2.0/arcstr/struct.ArcStr.html), whereas `&[&str]` requires that exactly `[&str]` exist somewhere.

---

<div class="post-metadata">

**Author:** ![simonbuchan](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/simonbuchan/32/9390_2.png) [@simonbuchan](https://internals.rust-lang.org/u/simonbuchan)\
**Post date:** [July 30, 2024, 3:05am UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/4 "2024-07-30T03:05:01Z")

</div>

(as I'm sure you know...) It's a standard pattern to have the generic version call an inner non-generic version, which addresses at least the code generation point. I have no idea what to search for, but I believe there was some noise about making that automatic?

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [July 30, 2024, 3:31am UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/5 "2024-07-30T03:31:10Z")

</div>

The term you're looking for is “polymorphization”.

---

<div class="post-metadata">

**Author:** ![ia0](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ia0/32/1396_2.png) [@ia0](https://internals.rust-lang.org/u/ia0)\
**Post date:** [August 3, 2024, 10:34am UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/6 "2024-08-03T10:34:02Z")

</div>

> [@kpreid](#):
>
> The place where `AsRef` is worth using today is for _elements of containers_

There's also cases where `Deref` doesn't suffice, like all the `std::fs` functions that take `AsRef<Path>` because there are so many things that may look like a `&Path` without having a `Deref` path. Here's an overview (for simplicity omitting `path::{Component{,s},Iter}` nodes and some `AsRef` edges, in particular those for transitive closures):

```plaintext
  &String --> &str
                |
                v
&OsString --> &OsStr horizontal is Deref
                ^ vertical is AsRef
                |
                v
 &PathBuf --> &Path

```

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [August 3, 2024, 9:59pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/7 "2024-08-03T21:59:19Z")

</div>

Honestly, the situation with `Deref`/`AsRef`/`Borrow` is far from ideal, a bit of a nest, and improving it doesn't seem all that likely. As far as I've intuited:

- `Deref`
  - Implement when `&Self` functionally is-a `&Target`. But note that this isn't "OOP is-a" (inherits API, may override behavior), it's "thin container is-a" (contains and manages the target, has nearly negligible identity beyond that).
  - Weakly implies that `Self` should impl `AsRef<Target>` and `Borrow<Target>`.
  - Don't take arguments generic over `Deref`, allow deref coercions to apply instead.
  - Don't take arguments of an `impl Deref` type when `&Target` would suffice.

- `AsRef<T>`
  - Implement for explicit conversion from `&Self` to `&T` for any "relevant" (typically unsized) `T` where the conversion is "cheap" (typically nothing more than logical place projection) and `&Self` is a fair substitute for `&T`.
  - Implement the reflective `AsRef<Self>` for any unsized types, as it's the most relevant for such.
  - Take arguments generic over `AsRef<T>` in convenience API that would otherwise take `&T` for some unsized `T` in the lower level guts.
  - Keep the function small before dispatch to the `&T` functionality for polymorphization purposes.
  - Honestly, should've been "take `&(impl AsRef<T> + ?Sized)`", but it wasn't and isn't, so functions that are incapable of utilizing it can take ownership, but stick to existing convention.
  - Should be kept as transitive as reasonably possible for concrete `T`.

- `Borrow<T>`
  - Implement when `&Self` should substitutable for `&T` in a way stronger than just `Deref` — namely that any other `trait` methods behave observably the same for the two types and can't be used to distinguish between `self` and `self.borrow()`.
  - Weakly implies that `&Self` should coerce to `&T` (when concrete, through some other mechanism).
  - Be generic over `Borrow` to generalize over owned or borrowed `T`, i.e. for `Cow` (copy-on-write).
  - Honestly, kind of got abused for its use in hashmap key lookups, which kind of wants for a more direct [key equivalence trait](https://docs.rs/hashbrown/latest/hashbrown/trait.Equivalent.html) instead of just borrow equivalence.

This understanding builds from a position that it is "wrong" for an API to take ownership if it would always be satisfied without ownership, because this leads to suggestions to use `f(s.clone())` where `f(&s)` would suffice. A different experience might hold a different position (e.g. for async spawn, you need to pass in owned values, so taking ownership even if you don't _require_ it can be a boon in some cases), but this is what I've built up.

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [August 3, 2024, 11:09pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/8 "2024-08-03T23:09:34Z")

</div>

> [@CAD97](#):
>
> - Honestly, should've been "take `&(impl AsRef<T> + ?Sized)`", but it wasn't and isn't,

No, it _ **was** _. `std` was changed from that form to the (worse, IMNSHO) ownership-taking form pre-1.0 on primarily aesthetic grounds (!). As a result and as you alluded to, the compiler will suggest you `pathbuf.clone()` instead of `&pathbuf` when you accidentally give up ownership.

If you eschew convention and go with `&(impl AsRef<T> + ?Sized)`, you avoid that particular downside.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [November 1, 2024, 11:09pm UTC](https://internals.rust-lang.org/t/better-syntax-for-the-std-convert-traits/21272/9 "2024-11-01T23:09:35Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
