Understanding Rust and Diesel: Handling nullable fields in models
While learning a new programming language, it can be easy to miss a little detail you'd never overlook in a familiar environment. The Rust's compiler is rather good at telling the user what they're doing wrong, but for a beginner it can be difficult to understand more advanced errors. I got one while going through the Diesel ORM Getting Started guide. Let's explore it!
--> src/models.rs:7:13n |
7 | pub id: i32,
| ^^^ the trait `FromSql<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>` is not implemented for `i32`
|
= help: the trait `FromSql<diesel::sql_types::Integer, Sqlite>` is implemented for `i32`
= note: required for `i32` to implement `diesel::Queryable<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>`
= note: required for `i32` to implement `FromSqlRow<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>`
= help: see issue #48214
My model:
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
My table:
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- NOT NULL is missing
title TEXT(64) NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT 0
)
Naturally, since the id
field is not marked as NOT NULL
, the value is optional. However, the struct states that the expected type for the id
field is i32
. Professionally I've been working with Java, where null values are part of the game. That's why I didn't even think about this discrepancy. In Rust, on the other hand, there are no null values.
Therefore, I should either make this field optional in the struct:
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Post {
pub id: Option<i32>, // allow null values
pub title: String,
pub body: String,
pub published: bool,
}
Or modify the table:
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, -- value must be specified
title TEXT(64) NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT 0
)
If you change the migration script, don't forget to recreate the schema with diesel migration redo
. Never modify the schema.rs
file manually.
When the issue is localized and solved, it's easier to understand the error:
--> src/models.rs:7:13
|
7 | pub id: i32,
| ^^^ the trait `FromSql<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>` is not implemented for `i32`
|
= help: the trait `FromSql<diesel::sql_types::Integer, Sqlite>` is implemented for `i32`
The schema.rs
file describes the table based on the actual sql request in the migrations/
directory. In my case, the id
field wasn't marked as NOT NULL
, so the schema specified it as nullable.
diesel::table! {
posts (id) {
id -> Nullable<Integer>,
title -> Text,
body -> Text,
published -> Bool,
}
}
The compiler detected the mistmatch between the schema and the struct:
--> src/models.rs:7:13n |
7 | pub id: i32,
| ^^^ the trait `FromSql<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>` is not implemented for `i32`
And even suggested that the i32
type has the following trait implemented:
= help: the trait `FromSql<diesel::sql_types::Integer, Sqlite>` is implemented for `i32`
Instead of FromSql<diesel::sql_types::Nullable<diesel::sql_types::Integer>, Sqlite>
.
Now I know what this means. Learning new stuff is fun! I can also recommend these two courses:
To my taste, the crash course was a bit shallow, because core topics like the errors handling are part of the intermediate course. But together these courses helped me in gaining confidence, and I enjoyed Nathan's style. The official book is deeper, of course, since it's a book. At the same time, it can be hard to consume and store all details in your memory. However, after taking these courses I can use the book to expand my knowledge.