Mapping struct parameters to another struct

Hi,

Again I am coming with my confusion for this new langage that I am learning :sweat_smile:

I am trying to map a struct 'Entity' to an other struct 'Model'.
The 'Entity' represents the data in a business view, and 'Model' represents the data in a storage/database view.

Code : Mapper
pub struct BookMapper {}

impl BookMapper {
    pub fn book_entity_to_model(book_entity: &BookEntity) -> BookModel {
        BookModel::new(
            book_entity.get_id(),
            book_entity.get_title(),
            book_entity.get_description(),
            book_entity.get_year(),
            book_entity.get_bar_code(),
            book_entity.get_photos(),
            book_entity.get_author(),
            book_entity.get_editor(),
        )
    }

    pub fn book_model_to_entity(book_model: &BookModel) -> BookEntity {
        BookEntity::new(
            book_model.get_id(),
            book_model.get_title(),
            book_model.get_description(),
            book_model.get_year(),
            book_model.get_bar_code(),
            book_model.get_photos(),
            book_model.get_author(),
            book_model.get_editor(),
        )
    }
}
Code : Entity and Model structs
#[derive(Clone)]
pub struct BookEntity {
    id: String,
    title: String,
    description: String,
    year: String,
    bar_code: String,
    photos: Vec<String>,
    author: String,
    editor: String,
}

impl BookEntity {
    pub fn new(id: String, title: String, description: String, year: String, bar_code: String, photos: Vec<String>, author: String, editor: String) -> Self {
        Self {
            id,
            title,
            description,
            year,
            bar_code,
            photos,
            author,
            editor,
        }
    }

    pub fn get_id(self) -> String { self.id }

    pub fn get_title(self) -> String { self.title }

    pub fn get_description(self) -> String { self.description }

    pub fn get_year(self) -> String { self.year }

    pub fn get_bar_code(self) -> String { self.bar_code }

    pub fn get_photos(self) -> Vec<String> { self.photos }

    pub fn get_author(self) -> String { self.author }

    pub fn get_editor(self) -> String { self.editor }
}

pub struct BookModel {
    id: String,
    title: String,
    description: String,
    year: String,
    bar_code: String,
    photos: Vec<String>,
    author: String,
    editor: String,
}

impl BookModel {
    pub fn new(id: String, title: String, description: String, year: String, bar_code: String, photos: Vec<String>, author: String, editor: String) -> Self {
        Self {
            id,
            title,
            description,
            year,
            bar_code,
            photos,
            author,
            editor,
        }
    }

    pub fn get_id(self) -> String { self.id }

    pub fn get_title(self) -> String { self.title }

    pub fn get_description(self) -> String { self.description }

    pub fn get_year(self) -> String { self.year }

    pub fn get_bar_code(self) -> String { self.bar_code }

    pub fn get_photos(self) -> Vec<String> { self.photos }

    pub fn get_author(self) -> String { self.author }

    pub fn get_editor(self) -> String { self.editor }
}

But when I try do that, Rust do not want because of a problem with a shared reference.
However, I use a reference to not own the object and I have try to clone parameters values, but it does not work... :confounded:

Rust error
error[E0507]: cannot move out of `*book_entity` which is behind a shared reference
  --> infrastructure\src\mappers\book_mapper.rs:10:13
   |
10 |             book_entity.get_id(),
   |             ^^^^^^^^^^^ move occurs because `*book_entity` has type `BookEntity`, which does not implement the `Copy` trait

Could you help me to understand what I did wrong please ?

Your getters all take ownership of self. That means that when you call them, the variable becomes invalid in the context in which it is called. Instead you should use &self in the signature, and return references to the fields. If the caller needs an owned copy, they can clone it themselves.

There's a few things going on here. First let's look at this method:

    pub fn get_id(self) -> String { self.id }

This takes self by value and returns a (owned, not borrowed) String. Because it takes self by value, once you call this method, the caller no longer owns the object. All they get back is the single field (self.id). That is:

    let id = object.get_id();
    // From this point on I've given up ownership of `object`
    // and can't use it any more

Instead, perhaps you wanted something like:

    // Temporarily borrow the id field
    pub fn get_id(&self) -> &str { &self.id }

Or

    // Get an owned clone of the field
    pub fn get_id(&self) -> String { self.id.clone() }

These both take self by shared reference (&self), so you don't lose ownership of the object.

When you go to convert between data structures, it's also important to keep in mind that there can only be one owner of the data at any time. So if you want both data structures to exist at once, you're going to have to clone the data in some way.

With get_id(&self) -> &str { &self.id } the caller will need to clone:

        BookModel::new(
            book_entity.get_id().clone(),
            book_entity.get_title().clone(),
            /* etc */
        )

With get_id(&self) -> String { self.id.clone() }, you already get a clone, so this works:

        BookModel::new(
            book_entity.get_id(),
            book_entity.get_title(),
            /* etc */
        )

But getters and setters can be problematic with Rust's ownership and borrowing rules; within a single crate, it's more common to just manipulate the fields directly:

        BookModel::new(
            book_entity.id.clone(),
            book_entity.title.clone(),
            /* etc */
        )

You only really need getters and setters if you need to enforce some sort of abstraction or invariant between the library and its users.

If you don't need both data structures at the same time, you could just do something like this:

        BookModel::new(
            book_entity.id,
            book_entity.title,
            /* etc */
        )

This will deconstruct book_entity -- you won't be able to use it anymore -- and it will use the pieces to make a new BookModel, without cloning. If this is what you want, I suggest looking into implementing the From trait.

For this situation in particular, I note that both structs have exactly the same fields. Perhaps it would benefit from factoring those fields out:

#[derive(Clone)]
pub struct BookData {
    id: String,
    title: String,
    /* etc */
}

#[derive(Clone)]
pub struct BookEntity {
    data: BookData,
}

impl From<BookData> for BookEntity {
    fn from(data: BookData) -> Self {
        Self { data }
    }
}

// Similar for BookModel

// Then you could also implement
impl From<BookEntity> for BookModel {
    fn from(entity: BookEntity) -> Self {
        Self { data: entity.data }
    }
}

// And if you wanted a cloning version
impl BookEntity {
    fn to_model(&self) -> BookModel {
        // Because we've grouped our common fields, we can clone that,
        // instead of each field.
        // Then because of our `From` implementation, we can use `into()`
        self.data.clone().into()

        // If you can't have the `From` implementation for some reason, this
        // could still be something like:
        // let data = self.data.clone();
        // BookModel::new(data.id, data.title, /* etc */)
    }
}

Thanks a lot, I do not see that I give self ownership in my getters... :confused:

But for the non-utility of getters, that was the same case in Java, but we do that to secure field accessibility (no overloading of value, ...). In that case, should I keep my getters, or is there another way to do that ?


I have already seen the 'From' implementation while seeking on the internet, but in my case I am not sure that this could be applyed.
In fact, in my example, fields are the same, but when I will go further in my application, I have to dissociate both structs because fields could not be the same.

I am trying to implemente the From<> trait, but i am blocked again due to trait :

pub trait LibraryElementEntity {}

pub trait LibraryElementModel {}

impl From<impl LibraryElementEntity> for LibraryElementModel {
    fn from(entity: impl LibraryElementEntity) -> Self;
}

Have you an idea if it is possible to do that ?

Another question, does a thing like From exists, but the inverse.
I am explaining :
The principle of my architecture (clean architecture) is to not have cross dependencies everywhere, but a specific dependency tree (business structs depends on nothing, data structs could depends on business, etc...)
So in my case, I want to map an Entity (business) to a Model (data) and vice versa.
But with From trait, I could do only in one way (from entity to model) but not the opposite.

If you need to secure field access, getters make sense. But within the library itself (where you can see private fields), it's more common to just access the fields directly, as otherwise it's easy to run into borrow checker violations. Getters borrow the entire struct, not just the field they return.

If possible it would be written out as

impl<T: LibraryElementModel, U: LibraryElementEntity> From<U> for T {
    fn from(entity: U) -> Self { todo!() }
}

But that's too unconstrained for Rust's non-overlapping-implementations and "orphan rules" (forward compatibility guards).

I don't know that I understand your goal, but there is the Into trait. Note that if you implement From<T> for U, then Into<U> for T is automatically implemented. Thus this note in the documentation:

One should avoid implementing Into and implement From instead. Implementing From automatically provides one with an implementation of Into thanks to the blanket implementation in the standard library.

Thanks again for yours answers.

Finally, I have modify my code to not have to implement From and Into traits on my LibraryElementModel.
So now I can implements them easily, and it is exactly what I need.

1 Like