Specializing on `Copy`

Does anyone know of techniques to specialize on Copy specifically in stable Rust? I am currently using the typeid crate to test whether a type is one of a known set of Copy types that are likely to be used with my library. Reading the Rust issues I have seen mentions of some libraries using hacks which rely on the fact that some standard library methods won't call the Clone impl for a type if it also implements Copy, but this is a cause for sadness amongst the stdlib maintainers.

(Edit: Fixed issue link)

It's best that you don't. Even if there are hacks that work today, they may break in the future.

3 Likes

Hmm indeed, it seems that day may come quite soon: stop specializing on `Copy` by joboet · Pull Request #135634 · rust-lang/rust · GitHub.

It looks like the typeid approach of specializing for a few concrete types (mainly primitive int and float types) with no lifetimes involved should still be OK.

Yup, this is fine. Basically all the problems come from subtyping because lifetimes.

That said, what are you hoping to do with the Copy-ness? Sometimes, for optimizations, just checking needs_drop::<T>() can be sufficient.

1 Like

That said, what are you hoping to do with the Copy -ness? Sometimes, for optimizations, just checking needs_drop::<T>() can be sufficient.

This is for various data copying operations in an ndarray-like library, where I'm copying chunks out of a source buffer into an uninitialized destination. [MaybeUninit<T>]::write_clone_of_slice would be useful for some of these when stabilized, if it reliably generated a memcpy for Copy types. There are also operations such as transposes, where an optimized implementation will load a tile of data into registers and shuffle it around, provided the type has a supported bit-width and is Copy.

You can use slice::clone_from_slice. The stdlib has specialization to use a memcpy when the type is Copy.

2 Likes

For concrete types, yes. But be careful about expanding this logic to some generic specialization on the assumption that : 'static means "no subtyping".

Rust does have subtyping between distinct : 'statictypes today, for example fn(&str) is a subtype of fn(&'static str).[1] If your specialization assumes subtyping can't occur, e.g. that a specialization for a subtype can never "switch" to the potentially unspecialized implementation of its supertype, unsoundness can occur. So you really do need "no lifetimes"[2] and not just "all 'static" to know there is no subtyping today, and AFAIK there is no way to generically require "no lifetimes".[3]

Additionally, the surface area of such subtype relationships may expand if we get non-lifetime binders (for<T: Bound> fn(T)), as that could introduce subtyping that does not involve lifetimes at all.

But if you're sticking to concrete types, the sub/super type relationships should remain more obvious. When using generics, introducing your own invariance can remove the possibility of subtyping instead.


  1. And similarly for higher-ranked dyn Trait types, so this is not a function pointer specific property. ↩︎

  2. or invariance ↩︎

  3. or that a generic is "invariant in everything", i.e. has no sub-or-supertypes other than itself ↩︎