API strategy : simpler vs intuitive

Hello
I'm learning RUST and it's my first post here, please be kind :slight_smile:

I'm creating a crate which use a database api (rusqlite). this rusqlite api offers a Connection api which is always non mut, even for database update (insert / update / ...). From my crate point of view, I would prefer to offer an api which shows internal mutability.

pub struct Store(rusqlite::Connection);

impl Store {
  // That seem more intuitive for the [Store] user
  pub fn add_item(&mut self, item: Item) {}

  // The rusqlite API allows me to do
  pub fn add_item(&self, item: Item) {}
}

I'm currently using the mut version, but I'd rather have your point of view : should I offer a simpler Store api (without mut) as rusqlite offer me to do so ?

And I have a similar question about async : As rusqlite interface is not async, should I let mine not async as well, or add async for all db access functions ?

Thanks

I prefer the second API design. If you don't need a mutable reference to do the job, then stick with it. The surface of the API should be as minimal as possible to get the job done.

The main purpose of mutable references is not mutabiility but unique access. It helped me a lot to read &mut in my head as "unique reference" instead of "mutable reference".

How would you implement an async wrapper for a non-async api?

1 Like

As my api could take some time (I do multiples database access in one function for example), I was wondering if it's better to clarify it with an async keyword

async doesn't mean that the function will take some time. It means that if the function does take some time, it should cooperatively yield to the async executor.

Marking a function as async but having it actually block on a database operation would be the worst of all choices.

3 Likes

Thanks, I think I have to study this part.

Note that sqlite is synchronous so this would not be the case anyway. To make it truly async, you would need to run sqlite on a different thread.

1 Like

One reason to use &mut might be if you ever expect to change the DB backend. Another backend might require mutability; you could simulate this with interior mutability, but that's not always what you want.

1 Like

How do you expect to use the Store?

Typically, my experience is that you want to share the one DB connection between a bunch of stuff. So being able to just have an Arc<Store> that you can use directly is worth it, since forcing people to deal with an Arc<RwLock<Store>> is just overhead and nuisance when the underlying implementation doesn't even need the locking.

This is very common in Rust -- note how you can Write to a &File, for example.

2 Likes

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.