Why does switching from struct to enum breaks API, exactly? - rust

I encountered an interesting change in a public PR.
Initially they had:
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct ParseError(ParseErrorKind);
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
enum ParseErrorKind {
OutOfRange // ... omitting other values here to be short
}
ParseError cannot be instantiated by clients because ParseErrorKind is private. They are making that enum public now, which seems ok, but I suggested an alternative: have ParseError be an enum itself, and leverage the type system instead of imitating it with the notion of "kind". They told me that would be an API breakage, and therefore was not ok.
I think I understand why in theory a struct and an enum are different. But I am not sure to understand why it is incompatible in this precise case.
Since the struct ParseError had no mutable field and cannot be instantiated by clients, there was nothing we could do with the type but to assign it and compare it. It seems both struct and enum support that, so client code is unlikely to require a change to compile with a newer version exposing an enum instead of struct. Or did I miss another use we could have with the struct, that would result in requiring a change in client code?
However there might be an ABI incompatibility too. How does Rust handle the struct in practice, knowing that only the library can construct it? Is there any sort of allocation or deallocation mechanism that requires to know precisely what ParseError is made of at buildtime? And does switching from that exact struct to an enum impact that? Or could it be safe in this particular case? And is that relevant to try to maintain the ABI since it is not guaranteed so far?

That's because every struct has fields, and hence this pattern will work for any struct, but will not compile with an enum:
struct Foo {}
fn returns_a_foo() -> Foo {
// anything that may return a Foo
}
if let Foo { .. } = returns_a_foo() {}
For example, this code compiles:
fn main() {
if let String { .. } = String::new() {}
}
Playground.
And while probably not code you'd write on your own, it's still possible to write, and additionally, possible to generate through a macro. Note that this is then, obviously, not compatible with an enum pattern match:
if let Option { .. } = None {
// Compile error.
}
Playground.

Related

How to augment structures with tag types in a way that doesn't prevent implementing Clone/Send/Sync in Rust?

In C++ I can use a template parameter as a tag, to make identical yet unrelated datatypes:
template<typename T>
struct UniqueId
{
int Value;
};
struct CustomerTag{};
struct BookTag{};
using BookId = UniqueId<BookTag>;
using CustomerId = UniqueId<CustomerTag>;
I can do the same thing in Rust, but run into problems because my type starts acting like it owns a T, which it does not. So now, in order to make my type Clone, Send, etc. my tags must also be Clone, Send, etc.. This is a little odd since my type doesn't really own the T, just uses it as a parameter. Is there any way around this? The documentation seems to suggest PhantomData<*const T> will fix this problem:
struct UniqueId<T> {
value: i32,
phantom: PhantomData<*const T>
}
but it doesn't seem to because then I just get *const BookTag cannot be sent between threads safely errors instead of BookTag cannot be sent between threads safely errors.
Use PhantomData<fn(T) -> T>, which is invariant over T and always Copy, Clone, Send, and Sync. The only downside is that you will have to write manual Copy and Clone implementations for your UniqueId<T> struct, because the derive macros currently always generate a T: Copy/T: Clone bound, even when it is unnecessary (see this issue).
Playground example

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

A macro registering marked structures into an enum

I am searching for a macro, or a procedural macro, which could register some marked structures (or enums) as variants of an enum.
Moreover, I would like, that if the marked structures are implementing a trait, then the trait would be implemented by this enum.
For example, I would like something like that:
trait MyTrait {
fn print_it(&self);
}
#[register(RegEnum,MyTrait)]
struct Foo;
#[register(RegEnum,MyTrait)]
struct Bar;
impl MyTrait for Foo {
fn print_it(&self) { println!("Foo"); }
}
impl MyTrait for Bar {
fn print_it(&self) { println!("Bar"); }
}
fn main() {
let reg_1 = RegEnum::Foo;
let reg_2 = RegEnum::Bar;
// This will print "Foo"
reg_1.print_it();
// This will print "Bar"
reg_2.print_it();
}
Notice that the macro should build the enum, by considering which structures are marked. The enum would not be defined by the programmer.
Is there a way to do that?
First, you have no way of knowing how much structs are annotated with the attribute. So you have to pass this number for each invocation.
The macro should cache the struct, and when that number is reached, it'll build and emit the trait. It should also error if the number is already reached, so you'll get future-proof against new structs instead of silent miscompilation.
Take a look in enum_dispatch's source for an example of a macro that does that.
Note that order of items is not guaranteed, and moreover, the macro can be called with only part of the items with incremental compilation. Also, as far as I am aware, there is no guarantee that the compiler will use the same process for each invocation, so theoretically it may break, but practically it works well (not sure whether it still works with incremental compilation, though).

How to offer an API that stores values of different types and can return them with the original type restored?

I want to offer a safe API like below FooManager. It should be able to store arbitrary user-defined values that implement a trait Foo. It should also be able to hand them back later - not as trait object (Box<dyn Foo>) but as the original type (Box<T> where T: Foo). At least conceptually it should be possible to offer this as a safe API, by using generic handles (Handle<T>), see below.
Additional criteria:
The solution should work in stable Rust (internal usage of unsafe blocks is perfectly okay though).
I don't want to modify the trait Foo, as e.g. suggested in How to get a reference to a concrete type from a trait object?. It should work without adding a method as_any(). Reasoning: Foo shouldn't have any knowledge about the fact that it might be stored in containers and be restored to the actual type.
trait Foo {}
struct Handle<T> {
// ...
}
struct FooManager {
// ...
}
impl FooManager {
// A real-world API would complain if the value is already stored.
pub fn keep_foo<T: Foo>(&mut self, foo: Box<T>) -> Handle<T> {
// ...
}
// In a real-world API this would return an `Option`.
pub fn return_foo<T: Foo>(&mut self, handle: Handle<T>) -> Box<T> {
// ...
}
}
I came up with this (Rust Playground) but not sure if there's a better way or if it's safe even. What do you think of that approach?

How can I add the to_string() functionality to an enum?

I am trying to create Error enums that implement to_string(). I have tried to derive(Debug) for them but it doesn't seem to be enough.
Here is the enum that I am working on:
#[derive(Debug, Clone)]
pub enum InnerError {
InnerErrorWithDescription(String),
}
#[derive(Debug, Clone)]
pub enum OuterError {
OuterErrorWithDescription(String),
}
What I am trying to make is:
// result type <T,InnerErrorWithDescription>
result.map_err(|err| { Error::OuterErrorWithDescription(err.to_string())}) // .to_string() is not available
I could not manage to convert InnerError enum type to OuterError.
What should I change to implement it?
I have made an example for writing enum types and their values' here:
Rust Playground
But, still I had to specify the type and it's description in match case, are there any more generic implementation?
Your enum should implement Display; from ToString docs:
This trait is automatically implemented for any type which implements
the Display trait. As such, ToString shouldn't be implemented
directly: Display should be implemented instead, and you get the
ToString implementation for free.
Edit: I have adjusted your playground example; I think you might be after something like this.

Resources