How to implement value-like semantics for non-trivial types in rust - rust

I would like to write a type that has an implementation for the Drop trait. but I would like to not need to call .clone() every time I need a copy like a type that implement Copy. But from what I understood, I cannot use the Copy trait because it can only be implemented for trivial types that can be memcpyed around and is incompatible with the Drop trait.
for example:
use std::rc::Rc;
#[derive(Clone)]
struct Impl {
//... details
}
#[derive(Clone)]
struct ValueLikeHandle {
handle : Rc<Impl>
}
impl ValueLikeHandle {
pub fn new() -> ValueLikeHandle {
ValueLikeHandle { handle : Rc::new(Impl{}) }
}
}
fn main() {
let a = ValueLikeHandle::new();
let b = a; // I would like this to be a .clone() with out writing it.
let c = a;
}
How can I implement value semantics for a non-trivial type ?

To the best of my knowledge you can't. This is a conscious design decision to prevent gotchas.
Clone has the potential to be expensive and therefore shouldn't happen implicitly. Also, it would be a gotcha because assignment either uses move semantics for non-copy types and copy semantics for copy types, but never clones.
In some languages you could overload the assignment operator to implement clone semantics, but Rust doesn't allow that.

Related

Is it appropriate to use a reference type in a From impl, like From<&Foo>

I have two types, call them Reusable and SingleUse, such that it is possible to construct a SingleUse from a Reusable. I'd like to enforce the invariant that the former can be reused while the latter is consumed by accepting a &Reusable or a SingleUse parameter. Something like:
let re = Reusable{};
let su = SingleUse{source:re};
do_something(&re);
do_something(&re);
do_something(su);
I can accomplish this by defining a From<&Reusable> for SingleUse and then taking a Into<SingleUse> for the do_something() parameter, but I'm not certain whether or not this is a misuse of From/Into. Most examples I've seen involving From don't mention using references. This article has an example of a From<&'a [T]> but still suggests that From/Into are intended to operate on values (and contrasts that with other traits like AsRef intended to operate on references).
So I'm hoping to sanity-check that using From on a reference type is appropriate, or if not if there's a more idiomatic way to model this Reusable/SingleUse relationship?
Full example (playground):
#[derive(Clone, Debug)]
struct Reusable { }
#[derive(Debug)]
struct SingleUse {
#[allow(dead_code)]
source: Reusable,
}
impl From<&Reusable> for SingleUse {
fn from(source: &Reusable) -> Self {
SingleUse{source: source.clone()}
}
}
fn do_something(input: impl Into<SingleUse>) {
println!("Saw:{:?}", input.into());
}
fn main() {
let re = Reusable{};
let su = SingleUse{source: re.clone()};
do_something(&re);
do_something(&re);
do_something(su);
}
std implements From<&str> for {Box<str>,Rc<str>,String,Arc<str>,Vec<u8>}, From<&String> for String, From<&[T]> for {Box<[T]>,Rc<[T]>,Arc<[T]>,Vec<T>} and some more. Especially note the From<&String> for String (essentially Clone) that is pretty similar to your situation. So I'd say this is fine.

How do I avoid Enum + Trait pattern when a struct is not object safe?

I get the implications of object safety, but I'm trying to find an idiomatic way to solve for this situation.
Say I have two structs that share common behavior and also need to derive PartialEq for comparison in another part of the program:
trait Growl:PartialEq {
fn growl(&self);
}
#[derive(PartialEq)]
struct Pikachu;
#[derive(PartialEq)]
struct Porygon;
impl Growl for Pikachu {
fn growl(&self) {
println!("pika");
}
}
impl Growl for Porygon {
fn growl(&self) {
println!("umm.. rawr?");
}
}
In another struct, I want to hold a Vec of these objects. Since I can't use a trait object with Vec<Box<Growl>>...
struct Region{
pokemon: Vec<Box<dyn Growl>>,
}
// ERROR: `Growl` cannot be made into an object
... I need to get more creative. I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case. Using an enum like this is what I've ended up doing but it feels unnecessarily complex
enum Pokemon {
Pika(Pikachu),
Pory(Porygon),
}
Someone coming through this code in the future now needs to understand the individual structs, the trait (which provides all functionality for the structs), and the wrapper enum type to make changes.
Is there a better solution for this pattern?
I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case.
Type erasure is just a synonym term for dynamic dispatch - even your original Box<dyn Growl> "erases the type" of the Pokemon. What you want here is to continue in the same vein, by creating a new trait better taylored to your use case and providing a blanket implementation of that trait for any type that implements the original trait.
It sounds complex, but it's actually very simple, much simpler than erased-serde, which has to deal with serde's behemoth traits. Let's go through it step by step. First, you create a trait that won't cause issues with dynamic dispatch:
/// Like Growl, but without PartialEq
trait Gnarl {
// here you'd have only the methods which are actually needed by Region::pokemon.
// Let's assume it needs growl().
fn growl(&self);
}
Then, provide a blanket implementation of your new Gnarl trait for all types that implement the original Growl:
impl<T> Gnarl for T
where
T: Growl,
{
fn growl(&self) {
// Here in the implementation `self` is known to implement `Growl`,
// so you can make use of the full `Growl` functionality, *including*
// things not exposed to `Gnarl` like PartialEq
<T as Growl>::growl(self);
}
}
Finally, use the new trait to create type-erased pokemon:
struct Region {
pokemon: Vec<Box<dyn Gnarl>>,
}
fn main() {
let _region = Region {
pokemon: vec![Box::new(Pikachu), Box::new(Porygon)],
};
}
Playground

Am I moving or cloning this String

I have an enum defined like this:
#[derive(Clone, Debug)]
pub enum JsonState {
NameReadingState(String),
StringState(String),
}
impl JsonState {
pub fn get_name_read(self) -> String {
if let JsonState::NameReadingState(name) = self {
return name;
} else {
panic!(
"Error: attempted to get name from non name state {:#?}",
self
);
}
}
}
If I were to call get_name_read on an instance of JsonState would the string be moved out of the enum or would it be copied? My understanding is that since I am passing self and not &self I am taking ownership of the instance inside the function and so I should be able to simply move the string out of it.
It is moved.
This is, in my opinion, one of the great advantages of Rust over C++: if you don't see a .clone() anywhere, then you are not cloning! In Rust, there are no implicit deep copies like in C++. If you want to create a copy/clone then you have to do it explicitly by calling a method that clones your instance.
All of this comes with one exception: types that implement Copy. These types use copy semantics instead of move semantics. It should be noted that Copy can only be implemented for types "whose values can be duplicated simply by copying bits", i.e. very simple types. String and any other types that manage heap memory do not implement Copy.

Is there a more idiomatic way to use traits like `io::Read` with lifetimes?

I have a public trait, Parser, that defines an external interface. I then have a private ParserImpl struct that implements the methods (actually, I have several implementations, which is the idea behind using the trait to abstract away).
use std::io;
pub trait Parser {
// ...omitted
}
struct ParserImpl<R: io::Read> {
// ...omitted
stream: R,
}
impl<R: io::Read> ParserImpl<R> {
// ...methods
fn new(stream: R) -> ParserImpl<R> {
ParserImpl {
// ...omitted
stream: stream,
}
}
}
impl<R: io::Read> Parser for ParserImpl<R> {
// ...methods
}
To create a parser instance, I use a function to hide ParserImpl.
pub fn make_parser<'a, R>(stream: R) -> Box<Parser + 'a>
where
R: io::Read + 'a,
{
Box::new(ParserImpl::new(stream))
}
This is all well and good... and it works... but the make_parser function troubles me. I feel that there must be a simpler way to approach this and like I'm missing something important, as this seems like a potential pitfall whenever using a trait like io::Read to abstract away the source of data.
I understand the need to specify lifetimes (Parameter type may not live long enough?) but I am a bit stumped on whether I can have both a clean and simple interface, and also use a trait like io::Read.
Is there a "cleaner," or perhaps more idiomatic way, to use traits like io::Read that I am missing? If not, that's okay, but I'm pretty new to Rust and when I wrote the above function I kept thinking "this can't be right..."
To make this sample runnable, here's a main:
fn main() {
use std::fs;
let file: fs::File = fs::File::open("blabby.txt").unwrap();
let parser = make_parser(file);
}
That is the idiomatic way of writing the code that has that meaning, but you may not want that meaning.
For example, if you don't need to create a boxed trait object, you can just return the parameterized value directly, or in this case just use the result of ParserImpl::new. This is my default form until I know I need dynamic dispatch provided by some trait object.
You could also require the 'static lifetime instead of introducing a new lifetime 'a, but this reduces the range of allowed types that you can pass into make_parser:
pub fn make_parser<R>(stream: R) -> Box<Parser>
where
R: io::Read + 'static,
{
Box::new(ParserImpl::new(stream))
}

Why doesn't String implement From<&String>?

Background
I know that in Rust people prefer &str rather than &String. But in some case we were only given &String.
One example is when you call std::iter::Iterator::peekable. The return value is a Peekable<I> object that wraps the original iterator into it and gives you one extra method peek.
The point here is that peek only gives you a reference to the iterator item. So if you have an iterator that contains Strings, you only have &String in this case. Of cause, you can easily use as_str to get a &str but in the code I will show below it is equivalent to a call to clone.
The question
This code
#[derive(Debug)]
struct MyStruct(String);
impl MyStruct {
fn new<T>(t: T) -> MyStruct
where
T: Into<String>,
{
MyStruct(t.into())
}
}
fn main() {
let s: String = "Hello world!".into();
let st: MyStruct = MyStruct::new(&s);
println!("{:?}", st);
}
doesn't compile because String doesn't implement From<&String>. This is not intuitive.
Why does this not work? Is it just a missing feature of the standard library or there are some other reasons that prevent the standard library from implementing it?
In the real code, I only have a reference to a String and I know to make it work I only need to call clone instead, but I want to know why.
To solve your problem, one could imagine adding a new generic impl to the standard library:
impl<'a, T: Clone> From<&'a T> for T { ... }
Or to make it more generic:
impl<B, O> From<B> for O where B: ToOwned<Owned=O> { ... }
However, there are two problems with doing that:
Specialization: the specialization feature that allows to overlapping trait-impls is still unstable. It turns out that designing specialization in a sound way is way more difficult than expected (mostly due to lifetimes).
Without it being stable, the Rust devs are very careful not to expose that feature somewhere in the standard library's public API. This doesn't mean that it isn't used at all in std! A famous example is the specialized ToString impl for str. It was introduced in this PR. As you can read in the PR's discussion, they only accepted it because it does not change the API (to_string() was already implemented for str).
However, it's different when we would add the generic impl above: it would change the API. Thus, it's not allowed in std yet.
core vs std: the traits From and Into are defined in the
core library, whereas Clone and ToOwned are defined in std. This means that we can't add a generic impl in core, because core doesn't know anything about std. But we also can't add the generic impl in std, because generic impls need to be in the same crate as the trait (it's a consequence of the orphan rules).
Thus, it would required some form of refactoring and moving around definitions (which may or may not be difficult) before able to add such a generic impl.
Note that adding
impl<'a> From<&'a String> for String { ... }
... works just fine. It doesn't require specialization and doesn't have problems with orphan rules. But of course, we wouldn't want to add a specific impl, when the generic impl would make sense.
(thanks to the lovely people on IRC for explaining stuff to me)
Since String does implement From<&str>, you can make a simple change:
fn main() {
let s: String = "Hello world!".into();
// Replace &s with s.as_str()
let st: MyStruct = MyStruct::new(s.as_str());
println!("{:?}", st);
}
All &Strings can be trivially converted into &str via as_str, which is why all APIs should prefer to use &str; it's a strict superset of accepting &String.

Resources