Rust ownership: create database wrapper with active record pattern

Viewed 45

Coming from PHP and freshly entering the realm of Rust, I'm trying to 'port' my legacy PHP code to Rust. Although I've spent many weeks trying to dive into the docs and try to write the code in Rust, it simply won't compile.

I've tried all options I could find on SO, including the Rc<RefCell<x>> construct. Obviously the PHP code has a bad design, but I can't see how to improve it. Any pointers in the right direction will be greatly appreciated!

PHP:

class Database {
  function build_row() : Row {
    return new Row($this);
  }
}
class Row {  // with AR properties
  protected $db;
  function __construct(Database $db) {
    $this->db = $db;
  }
}
$db = new Database;
$row = $db->build_row();

All of my attempts have failed because of the borrow checker:

pub struct Database {}
impl Database {
  pub fn new() -> Self { Database {} }
  pub fn build_row(&mut self) -> Row {
    Row::new(&mut self)
  }
}
pub struct Row<'a> {
  db: &'a mut Database,
}
impl<'a> Row<'a> {
  pub fn new(db: &'a mut Database) -> Self {
    Row { db }
  }
}
fn main() {
  let db = Database::new();
  let _row = db.build_row();
}

EDIT:

The problem can be solved by changing the thinking pattern.

PHP is OOP, with objects containing data and methods. Each object IS A thing. So that each Active Record object can own a mutable reference to the database connection object to perform operations on data in the database.

Rust's borrow checker does not allow this. Rust is behaviour oriented. There can be only one mutable reference at a time (in the same scope). Because of this, a different control flow is implicitly enforced. This is actually a good thing, although it requires a different mind set.

When modelling OOP classes to Rust structs, the structs and their implementations ACT AS A thing. This means that the Active Record structs can not own their own mutable reference to the database connection struct. Instead they have functions (like load(..) and save(..)) to which the mutable reference is passed, for whenever the AR struct needs to work with the database.

So the roort cause of the problem is that this difference between OOP and Rust is mostly presumed and not too explicitly spelled out. Which makes it harder to notice for developers like me, who are new to Rust and have spent too many years with OOP.

1 Answers

This actually doesn't compile for two very trivial reasons the compiler even points out. Your first problem is in the build_row method:

note: the binding is already a mutable borrow
 --> src/main.rs:4:20
  |
4 |   pub fn build_row(&mut self) -> Row {
  |                    ^^^^^^^^^
help: try removing `&mut` here
  |
5 -     Row::new(&mut self)
5 +     Row::new(self)

In Rust fn f(&mut self) is syntax sugar for fn f(self: &mut Self) where Self is the type you are currently implementing. This means that in the body of build_row the argument self has type &mut Database. When you try to pass Row::new &mut self you're trying to pass it a &mut &mut Database which (1) isn't the type it wants, and (2) isn't allowed by the borrow checker because it borrows the same thing mutably twice.

Like the hint in the error message suggests, you just need to write

pub fn build_row(&mut self) -> Row {
    Row::new(self)
}

instead.

Now you'll get a different error, this time in main:

error[E0596]: cannot borrow `db` as mutable, as it is not declared as mutable
  --> src/main.rs:18:14
   |
17 |   let db = Database::new();
   |       -- help: consider changing this to be mutable: `mut db`
18 |   let _row = db.build_row();
   |              ^^^^^^^^^^^^^^ cannot borrow as mutable

build_row needs to borrow db mutably, but it isn't declared as mutable. That's easy to fix, just declare it as let mut db = Database::new(); just as the compiler suggests.

Depending on where this implementation is headed, this design might not be a great fit for Rust overall, but that's hard to tell from just this snippet.

Related