Why does the borrow checker raise an error when I keep a reference to an element in a vector that is appended to within a loop?

It is true (AFAIK). And I even wrote a macro to cater to this scheme, like so:

macro_rules! table_creator {
    {
        $qtable_name:ident {
            $( $field:ident : $field_type:ty, )+ $(,)?
        },
        $dbtable_name:literal,
        $itable_name:ident $(,)?
    } => {
        #[derive(Queryable)]
        pub struct $qtable_name {
            pub id: i64,
            $( $field: $field_type, )+
        }

        #[derive(Insertable)]
        #[table_name = $dbtable_name]
        pub struct $itable_name<'a> {
            $( pub $field: &'a $field_type, )+
        }
    };
}

Anyways, thanks to everyone for helping me. I solved the issue, and learnt something new (and albeit important).

That's why I love Rust and The Rust community!

Just to be super clear here: Diesel does not force anyone to use references nor does it force you to have a separate struct for your insert operation at all.

To elaborate a bit on this:

Diesel accepts any field in a struct deriving Insertable as long as:

  • There is a matching column either by field name or by the explicitly specified #[column_name] attribute
  • The type of the field implements ToSql<diesel::dsl::SqlTypeOf<YourColumn>, YourBackend> You can lookup compatible types here. As you can see that list does include any owned type as well.

The documentation of the Insertable trait as more details here

Additionally as you seem to be upset about the fact that you "need" to have a separate struct for insert operations at all: It's perfectly valid to use #[derive(Insertable)] on the same struct you derived Queryable. This will not work for cases where you want to skip some fields for the Insertable implementation, as those fields don't have a value then.
Generally speaking #[derive(Insertable)] is designed for cases where you already have an existing struct that contains all data you want to insert. For example because those data are coming from an external API or the struct has a #[derive(Deserialize)] on it already. If that's not the case using the tuple insert variant is a valid option. All of this is described in the All about inserts guide on the diesel web page, so maybe spend some time to read that guide?

4 Likes

Thanks for the valuable information! I'll make sure I go through the docs.

Anyways, I learnt a different way to approach the problem with my scheme (although not the best solution).

After a little bit of fiddling around with the API, I'll change the existing codebase to adapt to an improved solution.

You should be able to use a wrapper like this with diesel instead of references:

use diesel::serialize::{ToSql, Result, Output};
use std::io::Write;

#[derive(Debug)]
struct RefToSql<'a, T> {
    value: std::cell::Ref<'a, T>,
}

impl<'a, T, A, DB> ToSql<A, DB> for RefToSql<'a, T>
where
    T: ToSql<A, DB>,
    DB: diesel::backend::Backend,
{
    fn to_sql<W: Write>(&self, out: &mut Output<'_, W, DB>) -> Result {
        self.value.to_sql(out)
    }
}
3 Likes

As the topic of custom diesel types impls comes up with this, let me shortly note that this is not everything that is required to use that custom type as part of an insert statement. The documentation of ToSql mentions this:

Any types which implement this trait should also #[derive(AsExpression)].

AsExpression describes how to transform a rust entity into an expression that could be used as part of any query. In contrast ToSql only describes how to serialize a given rust value. For rust values implementing ToSql a bind expression is generated. It's unfortunately due to overlapping generic impls not possible to let ToSql imply the corresponding AsExpression impl, so the extra derive/impl is required.

The documentation of AsExpression has more details about this derive:

This trait can be automatically derived for any type which implements ToSql. The type must be annotated with #[sql_type = "SomeType"]. If that annotation appears multiple times, implementations will be generated for each one of them.

This will generate the following impls:

  • impl AsExpression<SqlType> for YourType
  • impl AsExpression<Nullable<SqlType>> for YourType
  • impl AsExpression<SqlType> for &'a YourType
  • impl AsExpression<Nullable<SqlType>> for &'a YourType
  • impl AsExpression<SqlType> for &'a &'b YourType
  • impl AsExpression<Nullable<SqlType>> for &'a &'b YourType

As the impl written by @alice is a generic impl there are two options here:

  • Use the derive to generate a set of AsExpression impls for concrete types. (You can generate as many impls as you want by just repeating the #[sql_type] attribute with different values)
  • Write 6 generic impls of AsExpression manually. The implementation should just forward to the corresponding reference impls.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.