Can From trait implementations be lossy?
Clash Royale CLAN TAG#URR8PPP
Can From trait implementations be lossy?
I have a pair of related structs in my program, Rom
and ProfiledRom
. They both store a list of u8
values and implement a common trait, GetRom
, to provide access to those values.
Rom
ProfiledRom
u8
GetRom
trait GetRom
fn get(&self, index: usize) -> u8;
The difference is that Rom
just wraps a simple Vec<u8>
, but ProfiledRom
wraps each byte in a ProfiledByte
type that counts the number of times it is returned by get
.
Rom
Vec<u8>
ProfiledRom
ProfiledByte
get
struct Rom(Vec<u8>);
struct ProfiledRom(Vec<ProfiledByte>);
struct ProfiledByte
value: u8;
get_count: u32;
;
Much of my program operates on trait GetRom
values, so I can substitute in Rom
or ProfiledRom
type/value depending on whether I want profiling to occur.
trait GetRom
Rom
ProfiledRom
I have implemented From<Rom> for ProfiledRom
, because converting a Rom
to a ProfiledRom
just involves wrapping each byte in a new ProfiledByte
: a simple and lossless operation.
From<Rom> for ProfiledRom
Rom
ProfiledRom
ProfiledByte
However, I'm not sure whether it's appropriate to implement From<ProfiledRom> for Rom
, because ProfiledRom
contains information (the get counts) that can't be represented in a Rom
. If you did a round-trip conversion, these values would be lost/reset.
From<ProfiledRom> for Rom
ProfiledRom
Rom
Is it appropriate to implement the From
trait when only parts of the source object will be used?
From
I have seen that the standard library doesn't implement integer conversions like From<i64> for i32
because these could result in bytes being truncated/lost. However, that seems like a somewhat distinct case from what we have here.
From<i64> for i32
With the potentially-truncating integer conversion, you would need to inspect the original i64
to know whether it would be converted appropriately. If you didn't, the behaviour or your code could change unexpectedly when you get an out-of-bounds value. However, in our case above, it's always statically clear what data is being preserved and what data is being lost. The conversion's behaviour won't suddenly change. It should be safer, but is it an appropriate use of the From
trait?
i64
From
TryFrom
impl From<File> for Stdio
Given that the two types share a trait, it sounds like you don't really need to convert between them. You can just rely on the trait as a generic bound and worry about the concrete type at construction or certain entry points. If you're worried that implementing
From
here might a foot gun, then perhaps don't do it until you actually need it.– Peter Hall
Jul 10 at 17:49
From
This was discussed in chat.
– Jeremy Banks
Jul 10 at 20:35
RFC:
FromLossy
and TryFromLossy
traits– Jeremy Banks
Aug 1 at 15:04
FromLossy
TryFromLossy
1 Answer
1
From
implementations are usually lossless, but there is currently no strict requirement that they be.
From
The ongoing discussion at rust-lang/rfcs#2484 is related. Some possibilities include adding a FromLossy
trait and more exactly prescribing the behaviour of From
. We'll have to see where that goes.
FromLossy
From
For consideration, here are some Target::from(Source)
implementations in the standard library:
Target::from(Source)
Lossless conversions
Each Source
value is converted into a distinct Target
value.
Source
Target
u16::from(u8)
, i16::from(u8)
and other conversions to strictly-larger integer types.
u16::from(u8)
i16::from(u8)
Vec<u8>::from(String)
Vec<u8>::from(String)
Vec<T>::from(BinaryHeap<T>)
OsString::from(String)
char::from(u8)
Lossy conversions
Multiple Source
values may be convert into the same Target
value.
Source
Target
BinaryHeap<T>::from(Vec<T>)
Box<[T]>::from(Vec<T>)
and Box<str>::from(String)
lose any excess capacity.
Box<[T]>::from(Vec<T>)
Box<str>::from(String)
Vec<T>::from(VecDeque<T>)
loses the internal split of elements exposed by .as_slices()
.
Vec<T>::from(VecDeque<T>)
.as_slices()
Did I miss any important examples? Comments or edits welcome.
– Jeremy Banks
Aug 5 at 19:12
From<String>
for Box<str>
loses the (possibly extra) capacity.– trentcl
Aug 6 at 16:33
From<String>
Box<str>
@trentcl Fair edit --- it did look quite weird. Thanks for the example, that's a funny case to think about; I'll add it.
– Jeremy Banks
Aug 6 at 18:34
I went digging. The capacity is lost in
Vec::into_boxed_slice
, where it calls shrink_to_fit
. This is necessary so that the Box
can be safely dropped, but it means when you reconstitute the String
its capacity and length will always be the same.– trentcl
Aug 6 at 19:06
Vec::into_boxed_slice
shrink_to_fit
Box
String
@trentcl Thanks for looking into that. I guess this applies for the capacity of any
From<Vec<T>> for Box<[T]>
.– Jeremy Banks
Aug 6 at 20:23
From<Vec<T>> for Box<[T]>
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
The standard provide
TryFrom
when data could not be ok to convert, i64 => i8 for exemple. For me your problem is different, your main data is not lost. Standard also have some implementation of From who "lost" some information.impl From<File> for Stdio
for exemple, So I think, it's ok if it makes sense.– Stargateur
Jul 10 at 17:40