# Pre-ACP: Un-specialize impl ToString

**URL:** <https://internals.rust-lang.org/t/pre-acp-un-specialize-impl-tostring/20549>\
**Category:** libs\
**Created:** [March 28, 2024, 7:37am UTC](https://internals.rust-lang.org/t/pre-acp-un-specialize-impl-tostring/20549 "2024-03-28T07:37:56Z")\
**Posts on this page:** 1\
**Showing post:** 4

<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:** [March 28, 2024, 6:27pm UTC](https://internals.rust-lang.org/t/pre-acp-un-specialize-impl-tostring/20549/4 "2024-03-28T18:27:23Z")

</div>

[`Arguments::as_str`](https://doc.rust-lang.org/stable/std/fmt/struct.Arguments.html#method.as_str) is a similar "just for the purpose of optimization" API, so there is some precedent for adding shortcuts for the "essentially just a string" case.

> [@kornel](#):
>
> For example, if an implementation of `Display::fmt` only called `formatter.write_str(expr)`, then the compiler could transform it to `String::from(expr)`,

The main reason this is difficult AIUI is the dynamicism involved. Even if we ignore potential difficulties around reference validity guarantees\[1\], while it could be somewhat straightforward to replace `fn fmt(&self, &mut Formatter<'_>) -> Result` with `fn fmt(&self, &mut dyn Write + '_) -> Result`, the devirtualization to remove the `dyn` dispatch (required to actually DCE the `fmt` machinery) is much less straightforward.

That said, an MIR pass which attempts some amount of devirtualization would be an interesting project. AIUI most MIR opts have been focused on reducing the amount of IR passed to LLVM, and devirtualization would usually move in the other direction, but perhaps there's a heuristic that rustc could use that could remain a net positive?

After current MIR inlining, calling [`<&String as Display>::fmt(s, f)`](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=3c86944eb3632fa51cf8d002a891e67d) or [`<str as Display>::fmt(s, f)`](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=58b03ff6c40507c576edae1250139348) with `s: &&String, f: &mut Formatter` look the same, modulo debug information. A call to [`<&String as ToString>::to_string`](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=e4a39ddbcf247453a361aa5e5b9df75c) is just a call, whereas [`<str as ToString>::to_string`](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=fec8dfa92ef5ef35a0417a7178472324) is fully inlined. (playground links)

`ToString::to_string` is already marked `#[inline]` with a note that while unconventional for a generic impl, it has significant perf impact (ref: [#74852](https://github.com/rust-lang/rust/pull/74852)). `<&_ as Display>::fmt` does not have such an `#[inline]` annotation; perhaps adding it would enable `<&String as ToString>::to_string` to be inlined?

I recall seeing that the heuristic for auto-`#[inline]` is roughly that no MIR call statements exist in the optimized MIR. `<str as ToString>::to_string` obviously does include call ops (into allocation, as well as `Vec::deref`, interestingly\[2\]).

Subobservation: `Vec::deref` isn't known to not unwind. I would've hoped it'd just've been that MIR always includes unwind edges, but [`Vec::deref` has `-> [unwind continue]` where a `#[rustc_nounwind]` call has `-> [unwind unreachable]`](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=bf70a24c496364512fd99997ff9d8065). An MIR pass/opt to record cross-crate functions known to never unwind could potentially unblock some hidden optimizations, if not at the LLVM level, then at least at the MIR level.

Edit to add: [reported `Vec::deref` MIR inlining regression as an issue](https://github.com/rust-lang/rust/issues/123174)

* * *

1. I'm not sure exactly how relevant it is here, but it can be difficult to automatically optimize `fn(&Scalar)` into `fn(Scalar)` because while it's a validity requirement for the reference to be dereferenceable to sufficient bytes, there's no validity requirement for the bytes to be a valid instance of the scalar (currently; disclaimer: undecided, my own non-normative recollection, etc) _even if_ we derive proof that the address is irrelevant. 

2. And this is despite the function being marked as `#[inline]`. [Here it is open coded](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=bbfda71275825fb59bca05893633817f) (shows there aren't any reachable unwinding edges). Gut guess: the call to `std::slice::from_raw_parts::precondition_check` is blocking inlining 🙁 Justification: on stable, it inlines and doesn't include that call, making it a single straightline basic block, whereas it does include the UB check on beta and doesn't inline there.

---

_[View the full topic](https://internals.rust-lang.org/t/pre-acp-un-specialize-impl-tostring/20549)._
