Stream: project-safe-transmute

Topic: constraints on the design


gnzlbg (Dec 13 2019 at 12:21, on Zulip):

AFAICT there is no agreement on which problem this feature should solve, nor what are the constraints on the design (e.g. should this be a zero-cost abstraction, should this result in an explosion of impls, etc.). It would probably make sense to start by collecting examples and constraints before designing a proposed solution.

Ryan Levick (Dec 13 2019 at 12:40, on Zulip):

I think this is a good idea. Here's my first stab at this:
Problems:

constraints:

gnzlbg (Dec 13 2019 at 12:43, on Zulip):

The Compatible<T> RFC also lists some constraints on the design: https://gist.github.com/gnzlbg/4ee5a49cc3053d8d20fddb04bc546000#design-goals-and-constraints

These design goals and constraints are derived from the examples below:

gnzlbg (Dec 13 2019 at 12:47, on Zulip):

For example, IIUC, the safe transmute pre-RFCs violate most of these constraints.

gnzlbg (Dec 13 2019 at 12:47, on Zulip):

But by doing so the safe transmute pre-RFCs are able to do things that this RFC cannot.

gnzlbg (Dec 13 2019 at 12:49, on Zulip):

For example, with the safe transmute RFC you can transmute a &[u8] to a [u8; 16], but you can't replace that with an unsafe transmute::<[u8; 16], &[u8]> call to avoid checks because the two types have different sizes.

Ryan Levick (Dec 13 2019 at 12:52, on Zulip):

Just so we're on the same page, I think the RFC you posted is not fully correct when specifying the constraints that "safe-transmute-v2" proposes:

unnecessarily require run-time checks, e.g. bool -> #[repr(transparent)] struct Bool(bool)

Because of inlining the example you provided does not actually require runtime checks. I think a more appropriate way to state this is that it relies on the optimizer to do the right thing. So far, I've only found one case where runtime checks were not correctly eliminated by the optimizer (which I stated above). I'm not saying this isn't an issue, but the way it's stated is misleading.

ergonomics: many ""transmutes"" require multiple calls to the transmute methods, as opposed to plain transmute, e.g., bool -> Bool requires two transmute operations.

This is not true. The RFC provides a method that allows this to happen in one call (namely cast).

As for the other two constraints, I'm not sure I fully understand why those are a constraint on the design.

gnzlbg (Dec 13 2019 at 12:52, on Zulip):

Because of inlining the example you provided does not actually require runtime checks

In my constraint, this must be guaranteed, and not require run-time checks at any optimization level.

gnzlbg (Dec 13 2019 at 12:53, on Zulip):

That is, the constraint is that because this is a performance-only feature, such optimizations must be guaranteed and always apply.

Ryan Levick (Dec 13 2019 at 12:54, on Zulip):

Because of inlining the example you provided does not actually require runtime checks

In my constraint, this must be guaranteed, and not require run-time checks at any optimization level.

That's fair. I think though that it's important to state that the "safe-transmute-v2" proposal fails at this _only_ when the optimizer is not able to optimize many of the const expressions away. The way it reads currently makes it sound like a runtime check will always be performed when that's simply not true.

gnzlbg (Dec 13 2019 at 12:55, on Zulip):

It also fails at this when LTO is disabled, when optimizations are disabled, when the inliner fails, etc.

Ryan Levick (Dec 13 2019 at 12:55, on Zulip):

i.e., the choice is between gurantee of no runtime checks vs. best effort no runtime checks _NOT_ between no runtime checks and runtime checks

gnzlbg (Dec 13 2019 at 12:56, on Zulip):

As mentioned, my constraint requires guaranteeing no runtime checks, ever. I couldn't find anywhere in the RFC of safe-transmute v2 that guarantee, so that's why I mentioned that it does not guarantee that.

gnzlbg (Dec 13 2019 at 12:56, on Zulip):

If the safe-transmute RFC is changed to guarantee that such run-time checks will never happen, then I can change that part.

gnzlbg (Dec 13 2019 at 12:56, on Zulip):

But I understood safe-transmute v2 to be "best-effort" in that regard.

gnzlbg (Dec 13 2019 at 12:57, on Zulip):

And the tests in godbolt are done within the same translation unit. More interesting tests would be when the FromBytes/ToBytes trait are implemented in different crates, the impls aren't #[inline], and LTO is disabled.

Ryan Levick (Dec 13 2019 at 12:57, on Zulip):

I think you're misunderstanding what I'm saying. What you're stating here is correct, but your analysis in the RFC reads differently. Your analysis reads as if the runtime checks will always be performed not that there is not hard guarantee that they won't be performed. I believe this to be very different

gnzlbg (Dec 13 2019 at 12:58, on Zulip):

My analysis reads that rustc will always generate code to perform the runtime checks, and whether this code is removed or not would depend on a best effort optimization in the backend that might or might not happen.

gnzlbg (Dec 13 2019 at 12:58, on Zulip):

I can try to word that in such a way that makes it clearer that those run-time checks can sometimes be removed by the backend.

Ryan Levick (Dec 13 2019 at 12:59, on Zulip):

Yes that's all I'm stating. I think that would make it much clearer

gnzlbg (Dec 13 2019 at 13:00, on Zulip):

I've added a sentence to clarify that.

Ryan Levick (Dec 13 2019 at 13:01, on Zulip):

Thanks, and thoughts on the ergonomics point? From the user's perspective, casting between two types is a single call to cast.

Ryan Levick (Dec 13 2019 at 13:02, on Zulip):

I'm not sure I agree with your point that the proposal fails to provide an ergonomic ability to transmute

gnzlbg (Dec 13 2019 at 13:02, on Zulip):

@Ryan Levick how does that work for types with padding ?

gnzlbg (Dec 13 2019 at 13:02, on Zulip):

IIUC, that requires an implementation of ToBytes to exist

gnzlbg (Dec 13 2019 at 13:03, on Zulip):

but that cannot exist for, e.g., #[repr(C)] struct A { x: bool, y: u16 }, and then you can't cast that to #[repr(C)] struct B { x: Bool, y: u16 }

gnzlbg (Dec 13 2019 at 13:05, on Zulip):

Ah wait, that's a violation of the completeness constraint, not ergonomics.

Ryan Levick (Dec 13 2019 at 13:05, on Zulip):

Yes, I was about to write that. And I agree with that point

Ryan Levick (Dec 13 2019 at 13:06, on Zulip):

I believe that when the proposal doesn't fail on one of the other points, it does not fail on ergonomics

Ryan Levick (Dec 13 2019 at 13:07, on Zulip):

I'd also change the completeness point to reflect the example above ^^ and not the bool to Bool one currently in the document. While that might be technically true, I don't believe it captures why one should care like the example above does

gnzlbg (Dec 13 2019 at 13:08, on Zulip):

@Ryan Levick yes you are right, ergonomics are fine

gnzlbg (Dec 13 2019 at 13:08, on Zulip):

It doesn't suffer from N x M, since you only need ~ N + M implementations, to go from/to [u8]

gnzlbg (Dec 13 2019 at 13:09, on Zulip):

That's the best that can be done AFAICT

Ryan Levick (Dec 13 2019 at 13:09, on Zulip):

(by the way, it might be a good idea to get these two RFCs into the repo so we can collaborate through PRs as well). I'll need to convert the "safe-transmute-v2" pre-rfc into the RFC format so we can more easily compare

gnzlbg (Dec 13 2019 at 13:09, on Zulip):

sure I can send a PR

gnzlbg (Dec 13 2019 at 13:09, on Zulip):

the RFC is not ready though

gnzlbg (Dec 13 2019 at 13:10, on Zulip):

There are some degrees-of-freedom that some RFCs have, that I haven't been able to word as constraints

gnzlbg (Dec 13 2019 at 13:10, on Zulip):

for example, TryCompatible has an associated error type

gnzlbg (Dec 13 2019 at 13:10, on Zulip):

That solves some problems that safe-transmute v2 has, like being able to express that some operations cannot fail by using !

gnzlbg (Dec 13 2019 at 13:10, on Zulip):

But it introduces other problems

Ryan Levick (Dec 13 2019 at 13:11, on Zulip):

All alternatives discussed below require N x M trait impls.

This is not true for the safe-transmute-v2 proposal

gnzlbg (Dec 13 2019 at 13:11, on Zulip):

Like it makes it impossible to provide something like ::cast

gnzlbg (Dec 13 2019 at 13:14, on Zulip):

A middle ground solution would be to only allow certain types as the associated type

gnzlbg (Dec 13 2019 at 13:15, on Zulip):

e.g. one can allow ! or a specific Result type, by bounding the associated error type with a sealed trait that's only implemented for those two.

Ryan Levick (Dec 13 2019 at 13:15, on Zulip):

I believe the "safe-transmute-v2" proposal does better in terms of trait impls. Each type has at most 3 trait impls (FromAnyBytes, FromBytes and ToBytes). The "compatible" proposal requires each trait to have an unbounded number of impls for every type it can possibly be compatible with. Am I wrong in thinking this?

gnzlbg (Dec 13 2019 at 13:16, on Zulip):

Yes, you are wrong.

gnzlbg (Dec 13 2019 at 13:16, on Zulip):

The compatible trait proposal only requires one trait impl, either Compatible<T> or TryCompatible<T>, per type.

gnzlbg (Dec 13 2019 at 13:17, on Zulip):

If T is [u8] then you can go to any type that's compatible with that

gnzlbg (Dec 13 2019 at 13:18, on Zulip):

For example, if you have a type struct Foo([u8; 4]); and implement Foo: Compatible<[u8; 4]>, and you have some other type Bar such that [u8; 4]: Compatible<Bar>, then you can go from Foo to Bar.

gnzlbg (Dec 13 2019 at 13:19, on Zulip):

Even though there is no Foo: Compatible<Bar> impl.

Ryan Levick (Dec 13 2019 at 13:20, on Zulip):

I would not have imagined the type system was capable of following through all those relationships. I'm assuming you've tested this and it works?

gnzlbg (Dec 13 2019 at 13:20, on Zulip):

I asked in the chalk zulip, and they said that such a query is probably possible, similar queries exist

Ryan Levick (Dec 13 2019 at 13:21, on Zulip):

Is it currently possible? Because it should be explicitly noted that this would require changes to the type inferencer if that's the case

gnzlbg (Dec 13 2019 at 13:21, on Zulip):

They are unsure about how exactly formulate the query in a recursive way

gnzlbg (Dec 13 2019 at 13:22, on Zulip):

But since chalk is prolog-like, it must be possible to ask whether such a sequence exist

gnzlbg (Dec 13 2019 at 13:22, on Zulip):

It is noted that the meaning of T: Compatible<U> bound is special.

gnzlbg (Dec 13 2019 at 13:22, on Zulip):

And the precise definition of that trait query is noted in the RFC.

Matthew Jasper (Dec 13 2019 at 13:23, on Zulip):

There's also the issue that it can result in non-deterministic lifetime bounds, unless there's some well-defined search order. Which IMO makes it a non-starter, even if no one will ever write a lifetime-dependent Compatible impl.

gnzlbg (Dec 13 2019 at 13:23, on Zulip):

@Matthew Jasper it is non-deterministic, but the RFC argues that it doesn't matter

gnzlbg (Dec 13 2019 at 13:23, on Zulip):

is that argument incorrect ?

gnzlbg (Dec 13 2019 at 13:24, on Zulip):

If you have a transmute::<T, U>(), for that to be safe, it suffices to exist a chain of transmutes from U to T that are safe

gnzlbg (Dec 13 2019 at 13:24, on Zulip):

if there are multiple chains, it doesn't really matter which one you pick

Ryan Levick (Dec 13 2019 at 13:24, on Zulip):

@gnzlbg I'm not seeing where that's plainly stated. Can you point me to that?

gnzlbg (Dec 13 2019 at 13:24, on Zulip):

In the trait query specification

gnzlbg (Dec 13 2019 at 13:25, on Zulip):

Notice that multiple such sequences could exist, but it suffices that one exists for the query to be satisfied.

Matthew Jasper (Dec 13 2019 at 13:25, on Zulip):

There problem is that with lifetimes you don't know whether a sequence exists until it's too late.

Ryan Levick (Dec 13 2019 at 13:25, on Zulip):

Ah sorry, I meant where it's plainly stated that the trait resolution is not currently possible but would need to be added to have this feature

Ryan Levick (Dec 13 2019 at 13:26, on Zulip):

I see where it's implied if you're familiar with how things currently work, but it would be nice to have this spelled out explicitly

gnzlbg (Dec 13 2019 at 13:26, on Zulip):

In the drawbacks section, it does say "Complicates the trait system"

gnzlbg (Dec 13 2019 at 13:27, on Zulip):

That section is obviously incomplete, somebody should expand on that, mentioning that a new kind of query with the semantics of the reference section have to be added, etc.

gnzlbg (Dec 13 2019 at 13:27, on Zulip):

There problem is that with lifetimes you don't know whether a sequence exists until it's too late.

Can you elaborate?

gnzlbg (Dec 13 2019 at 13:27, on Zulip):

Maybe an example would help making that clearer.

gnzlbg (Dec 13 2019 at 13:28, on Zulip):

(e.g. i'm imagining impl<'a, T, U> Compatible<&'a U> for &'a T { } where T: Compatible<U> but I don't see any big issues with that)

gnzlbg (Dec 13 2019 at 13:29, on Zulip):

Maybe impl<'a, 'b, T, U> Compatible<&'a U> for &'b T where T: Compatible<U>, 'b: 'a {} ?

Matthew Jasper (Dec 13 2019 at 13:33, on Zulip):

So if there are impls like Compatible<&'a U> for &'b U and Compatible<U> for U then depending on the impl we choose converting &'0 U to &'1 U may require the regions to be equal, or only that one outlives the other. (It doesn't matter here due to variance, but that's besides the point).

gnzlbg (Dec 13 2019 at 13:35, on Zulip):

@Matthew Jasper I thought it would be the other way around

gnzlbg (Dec 13 2019 at 13:35, on Zulip):

If one writes safe_transmute::<&'a U, &'a U> then the regions are required to be equal, while if one does safe_transmute::<&'a U, &'b U> then one is required to out-live the other.

rkruppe (Dec 13 2019 at 13:36, on Zulip):

You can link me to the Discussion in the Chalk Zulip you mentioned? It seems clear that this can be implemented without too much trouble, but I'm not sure it will be better for compiler performance than having N x M impls manifest in the program. The obvious way to do it (which side-steps all worries about degrading the neat properties Chalk has today) is to add a transitivity rule like (?T : Compatible<?U>) :- (?T : Compatible<?V>), (?V : Compatible<?U>). But I worry that setting Chalk loose on this won't be particularly efficient, because Chalk enumerates answers to trait queries breadth-first. BFS is not great at finding paths between a specific pair of nodes quickly, and although it amortizes pretty well (assuming memoization, which Chalk obviously has) if you compute the reachability matrix of the whole graph, doing that would require (asymptotically) as much time and memory as generating all the N x M impls before going into trait solving.

gnzlbg (Dec 13 2019 at 13:37, on Zulip):

If one writes safe_transmute::<&'a U, &'a U> then the regions are required to be equal, while if one does safe_transmute::<&'a U, &'b U> then one is required to out-live the other.

I wouldn't expect the lifetimes to be dependent on with chain of trait impls is picked, but rather, for the lifetime requirements to constraint which chain of trait impls are valid. But maybe I'm misunderstanding the order in which lifetimes and traits are resolved ?

Matthew Jasper (Dec 13 2019 at 13:38, on Zulip):

I may have swapped them. Free lifetime inference is always the last part of type checking.

gnzlbg (Dec 13 2019 at 13:39, on Zulip):

@rkruppe I kind of expect each type to have very few Compatible impls, and typically towards some type like a [u8; N] that "maximizes" how many safe transmutes they get for free

gnzlbg (Dec 13 2019 at 13:40, on Zulip):

So that's indeed quite bad due to what you said

gnzlbg (Dec 13 2019 at 13:40, on Zulip):

Once you reach such a type, there are many edges that must be visited to find the type you want to convert to

gnzlbg (Dec 13 2019 at 13:40, on Zulip):

even if the conversion is only two graph edges away

gnzlbg (Dec 13 2019 at 13:40, on Zulip):

because these nodes in the middle have lots of edges

Ryan Levick (Dec 13 2019 at 13:41, on Zulip):
TryCompatible<&[u8; {size_of::<T>()}]>

One benefit of Compatible<T> is that its future compatible with const generics. For the other RFC we would need to make the choice if we want to be dependent on const generics or not

gnzlbg (Dec 13 2019 at 13:45, on Zulip):

@Matthew Jasper

I may have swapped them. Free lifetime inference is always the last part of type checking.

So I'm not sure how that would work. From the constraint, multiple chains might be valid, and each chain might have different life-time requirements, and that might impact lifetime inference later on, since there are multiple ways to infer a particular lifetime. I'm not sure if by that time it matters, e.g., if the two possibilities are that either one lifetime must outlive the other, or be independent, and both are "ok", then does it matter? And if only one is ok, the other cannot be inferred, right?

Lokathor (Dec 13 2019 at 21:24, on Zulip):

@Ryan Levick I think that bytemuck actually hits most of your goals already, except that it is deliberately very conservative in the design and mostly doesn't tackle the issue of types with limited bit patterns. Such things could be added without changing the fundamental design though (one such extension to support limited bit pattern types is in PR review phase actually).

I think that a lot of the safe transmuting can be handled with marker traits to give what a type supports and then a small collection of trait bound free functions that do the work. This lets us leverage parametric generics as much as possible to do a lot of the hard thinking for us.

I would also like to add that transmuting &[u8] to &T seems to get mentioned a lot but it's not even the type of cast that was most requested before I made my crate. People were usually asking for slice to slice casting and owned to owned casting. I hope those casts can be remembered more often as we discuss.

Ryan Levick (Dec 16 2019 at 12:26, on Zulip):

I would also like to add that transmuting &[u8] to &T seems to get mentioned a lot but it's not even the type of cast that was most requested before I made my crate

This is certainly interesting. The original use case I was coming from that prompted me to start the whole RFC process was one in which the programmer memcopies a large buffer (probably from the network) and then wants to safely view pieces of that buffer in a structured way. This is directly the &[u8] to &T use case. I see &[u8] to &[T] as a direct extension of that as well.

What are some of the high level use cases for &[T] to &[U] and T to U casting? I believe both proposals being discussed support these use cases, but I want to make sure I have a good understanding of the actual need for this.

Ryan Levick (Dec 16 2019 at 13:01, on Zulip):

@Lokathor I also don't see a huge ton of difference between the "safe-transmute" pre-RFC and bytemuck (we only didn't mention it in the pre-RFC because we somehow missed it - we'll mention it in the RFC should we go this direction). I believe bytemuck simplifies by removing the following features which are available in the pre-RFC:

Lokathor (Dec 16 2019 at 16:03, on Zulip):

I first came to this general problem of safe transmutation because gfx-hal had a "cast_slice" operation that was actually really unsound, so I wrote a correct version. I stuffed it into my "bag of utils" crate and forgot about it for months until the lead of Safety Dance saw it and said "actually you should put just that one thing in its own crate because people are out here casting slices all the time and getting it wrong all the time".

I've specifically seen people ask for:

I didn't even put &[u8] to &T into bytemuck 1.0 because I guess I didn't talk to enough parsing people or something, but also that's just a mild specialization of a slice cast. For the first pass of my PNG parser, where I probably would have used &[u8] to &T, I just wrote

let (here, more) = more.split_at(4);
let arr = *cast_slice::<u8, [u8;4]>(slice)[0];

to pull out [u8;4] arrays from the data steam. Perfectly ergonomic? No, definitely not, but it does show that slice casting is probably the most "fundamental" of all the casts. SimonSapin actually threw a &[u8] to &T PR at me the day they saw my crate, so that's in 1.1 now.

As to your bullet points: * For "cast from X into bytes", I considered making a trait for that which is a parent trait of Pod, similar to how Zeroable gets its own sub-trait. People often want to just zero a thing as cheap initialization, so I split if off as its own ability. However, when I asked folks, "hey what if there was a trait for linear memory that was totally initialized? That could be a lesser contract than Pod." people said "what's the point?" and I said "Well it'd let you have this one special method as_bytes where you can turn any &T into &[u8]". Not a single person I talked to was impressed with the concept. No one thought that it was worth having a dedicated trait, so I just dropped the concept for lack of demand. It's certainly something you could fit into the design if anyone actually does care. * Deriving it all is really, really hard without compiler support :P Though someone added a Zeroable derive. Of course I'd like to see more happen here, * Fallible casting actually has an open PR right now if you want to see one possible design. It's really focused on enums and enum-likes just because of how the person who wrote it wants to use it. They need it for somewhere deep inside some huge mostly-C++ codebase where Rust and C++ talk to each other. The PR's particular design allows bool, but it doesn't allow for char or NonZeroI32. Again, it's largely an artifact of wanting to be fast (minimal checks) while also being absolutely correct without fail, and in the absence of compiler support we end up just covering the cases that we know we can do correctly as a crate. I think that with compiler support this would become a lot easier. On the other hand, I asked on the #design channel of the Official Discord a few weeks ago, "hey what about the idea of compiler support for having some bits of some amount and then being able to ask the compiler 'if I wanted to put these bits within some enum or other limited bit pattern type, what value would they be in that type?" and at the time people essentially said "that sounds so niche I don't think we'd bother to spend the time to implement that ability". So hopefully this project group can make the issue seem worth the time.

(This post got super long but I hope I was clear about everything.)

Ryan Levick (Dec 16 2019 at 16:46, on Zulip):

@Lokathor Thanks for the clarifications! Question: is the limitation that some types aren't Pod but could be casted to (e.g., bool) acceptable to you? The reason we introduced the ToBytes wasn't because we thought it's interesting to turn a type into bytes, but because it's sometimes necessary to distinguish between types that are Pods (using your definition for what that is) and types that aren't but reading them always gives you well defined bytes

Lokathor (Dec 16 2019 at 17:14, on Zulip):

I have nothing fundamentally against the idea. People just told me no one cared, so I skipped over it. I could even put it into bytemuck as a proof of concept if you'd like to see it set up in practice.

Though I'll note: for going from u8 to bool it should just be a TryFrom impl. Any "do a cast but we have to perform a validity check" should just use the existing TryFrom trait as much as possible. That's what it's there for. And it makes the way you can use a type very discoverable when you look in the docs.

Lokathor (Dec 16 2019 at 17:21, on Zulip):

(though, without const generics, the only thing bytemuck could do with a "LinearInitialized" marker trait is some sort of view_bytes function for &T to &[u8]. It'd probably get really complicated if you wanted to do owned to owned casting.)

gnzlbg (Dec 16 2019 at 18:25, on Zulip):

I'm not sure I agree with the TryFrom recommendation

gnzlbg (Dec 16 2019 at 18:26, on Zulip):

TryFrom does a "value preserving" conversion, while transmute re-interprets some memory as being of a different type, and is "bit pattern preserving"

gnzlbg (Dec 16 2019 at 18:27, on Zulip):

For some types, like going from a u8 to a bool, both things have the same semantics, but this is not true for all types.

gnzlbg (Dec 16 2019 at 18:29, on Zulip):

So if both operations are different and make sense exposing, then we need two ways to expose them, and then the question becomes whether for types for which both operations mean the same thing, both should still be exposed or not.

Lokathor (Dec 16 2019 at 18:51, on Zulip):

That's fair. For other types it might be a lot less correct for TryFrom to be used.

Josh Triplett (Dec 16 2019 at 19:38, on Zulip):

Agreed; TryFromis for semantic conversions that might change the memory values, and it doesn't guarantee that it'll do an allocation-free reinterpretation of memory.

gnzlbg (Dec 16 2019 at 21:48, on Zulip):

This touches a bit on a tangential issue, which is how can we encourage implementations of whatever mechanism we use to be "allocation-free" (or more generally, just free)

gnzlbg (Dec 16 2019 at 21:49, on Zulip):

I think one invariant that kind of enforces this is to allow replacing whatever mechanism we use with an unsafe { transmute::<T, U>(x) }

gnzlbg (Dec 16 2019 at 21:49, on Zulip):

But that feels to restrictive, since it require both T and U to have the same size

gnzlbg (Dec 16 2019 at 21:50, on Zulip):

(e.g. it won't work for &[T] to &[T; N] because both references have different sizes)

Lokathor (Dec 16 2019 at 21:54, on Zulip):

It seems fair to classify &[T] to &U as a different "type" of transmute which we can simply support separately from non-referencing T to U.

gnzlbg (Dec 17 2019 at 11:42, on Zulip):

@Lokathor to me it feels more like a coercion than a transmute

gnzlbg (Dec 17 2019 at 11:43, on Zulip):

We are coercing a fat pointer &[T] into a thin pointer, e.g., &[T; N].

gnzlbg (Dec 17 2019 at 11:44, on Zulip):

I think there is also a third case: coercing &[T] to &[U], where the coercion is not just a "bitwise" copy, but it might involve changing the bits of, e.g., the length of the slice (e.g. for going from &[u8] to &[u16]).

gnzlbg (Dec 17 2019 at 11:47, on Zulip):

The main problem with doing "some work" is that the work can fail. I'm not sure if this is true for going from &[u16] to &[u8], but one thing that could happen is that this coercion could fail if, e.g., doubling the length overflows isize, but whether that can actually happen might depend on which limits we impose to the lengths of allocations (e.g. if an allocation can at most be isize bytes, such overflow cannot happen). So there might be some subtle interaction between coercions that do some work, and unspecified parts of the Rust abstract machine, like the largest size of a Rust object or allocation.

Lokathor (Dec 17 2019 at 15:18, on Zulip):

that's already a general limit on slices (no more than isize bytes), but I guess it's not an allocation limit in general. Still, if you have any slice in rust it's span is isize::MAX bytes or less

Lokathor (Dec 17 2019 at 15:19, on Zulip):

But yeah, that's why I like "casting", because people understand fuzzily that a cast is "free, or maybe a tiny amount of work"

Last update: Jan 28 2020 at 00:35UTC