Paul Knopf
Paul Knopf

Reputation: 9776

Rust: Passing borrowed struct to a borrowed enum?

I'm trying to pass a borrowed struct into a borrowed enum.

#[derive(Copy, Clone)]
pub struct CustomerData {
    // Many fields about customers
}

#[derive(Copy, Clone)]
pub struct EmployeeData {
    // Many fields about employees
}

pub enum Person {
    Customer(CustomerData),
    Employee(EmployeeData)
}

fn do_something_with_customer(customer: &CustomerData) {
    let person = &Person::Customer(customer);

    // This would work, but this can be a large struct.
    // let person = &Person::Customer(customer.clone());

    general_method(person);
}

fn do_something_with_employee(employee: &EmployeeData) {
    let person = &Person::Employee(employee);

    // This would work, but this can be a large struct.
    // let person = &Person::Employee(employee.clone());

    general_method(person);
}

fn general_method(person: &Person) {

}

fn main() {
    let person = Person::Customer(CustomerData { });

    match &person {
        Person::Customer(data) => {
            do_something_with_customer(data);
        }
        Person::Employee(data) => {
            do_something_with_employee(data);
        }
    }
}

Compiling gives me the result:

error[E0308]: mismatched types
  --> src/main.rs:19:36
   |
19 |     let person = &Person::Customer(customer);
   |                                    ^^^^^^^^
   |                                    |
   |                                    expected struct `CustomerData`, found reference
   |                                    help: consider dereferencing the borrow: `*customer`
   |
   = note: expected type `CustomerData`
              found type `&CustomerData`

error[E0308]: mismatched types
  --> src/main.rs:28:36
   |
28 |     let person = &Person::Employee(employee);
   |                                    ^^^^^^^^
   |                                    |
   |                                    expected struct `EmployeeData`, found reference
   |                                    help: consider dereferencing the borrow: `*employee`
   |
   = note: expected type `EmployeeData`
              found type `&EmployeeData`

I get that the Rust compiler isn't allowing me to do this, but I feel like I should be able to, considering the enum I'm passing my structs to is borrowed as well.

Is there a pattern/workaround for this scenario? Maybe using the Rc type? I'd hate to litter my code with it for this scenario.

use std::rc::Rc;

#[derive(Copy, Clone)]
pub struct CustomerData {
    // Many fields about customers
}

#[derive(Copy, Clone)]
pub struct EmployeeData {
    // Many fields about employees
}

pub enum Person {
    Customer(Rc<CustomerData>),
    Employee(Rc<EmployeeData>)
}

fn do_something_with_customer(customer: Rc<CustomerData>) {
    let person = &Person::Customer(customer);

    // This would work, but this can be a large struct.
    // let person = &Person::Customer(customer.clone());

    general_method(person);
}

fn do_something_with_employee(employee: Rc<EmployeeData>) {
    let person = &Person::Employee(employee);

    // This would work, but this can be a large struct.
    // let person = &Person::Employee(employee.clone());

    general_method(person);
}

fn general_method(person: &Person) {

}

fn main() {
    let person = Person::Customer(Rc::new(CustomerData { }));

    match &person {
        Person::Customer(data) => {
            do_something_with_customer(data.clone());
        }
        Person::Employee(data) => {
            do_something_with_employee(data.clone());
        }
    }
}

Upvotes: 1

Views: 740

Answers (1)

S&#233;bastien Renauld
S&#233;bastien Renauld

Reputation: 19662

You have misidentified the problem and the compiler was spot-on on its error comments.

You defined your enum like so:

pub enum Person {
    Customer(CustomerData),
    Employee(EmployeeData)
}

But then you decide that your enum member should be Person::Customer(&CustomerData):

fn do_something_with_customer(customer: &CustomerData) {
    let person = &Person::Customer(customer);

References are not transitive. Because &CustomerData is a reference does not mean that the entire enum will be a reference to real data (i.e. &Person::Customer(CustomerData)).

There are two ways to fix it; the obvious is to see if CustomerData implements Copy. If it does, you can just dereference (and therefore implicitly copy):

fn do_something_with_customer(customer: &CustomerData) {
    let person = Person::Customer(*customer);

(This is what the compiler suggested, so I'm pretty sure your type implements Copy)

The other option is to #[derive(Clone)] on the type and call customer.clone(). Again, at the cost of an extra allocation.

If you really want the reference in the enum, you need to change the enum definition to:

pub enum Person<'a> {
    Customer(&'a CustomerData),
    Employee(&'a EmployeeData)
}

And handle the fact that the object property is now a reference, with all the problems associated.

Upvotes: 4

Related Questions