Can From trait implementations be lossy?

The name of the pictureThe name of the pictureThe name of the pictureClash 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





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



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.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard