Making struct's field either foreign key or full object

Say we are interacting with a database which supports foreign keys.

For the sake of example, let it be a simple schema, like so:

    Users                Posts
┌────┬─────────┐     ┌────┬──────┐
│ id │ post_id ├────►│ id │ text │
├────┼─────────┤     ├────┼──────┤
│    │         │     │    │      │
│    │         │     │    │      │
└────┴─────────┘     └────┴──────┘

Sometimes, we need to fetch users with only post_id, so we write a simple SQL query and put all the data into struct User:

struct User {
  id: isize,
  post: PostId,
}

type PostId = isize;

Other times, we require a full post, so we have to join the Posts table and put the entire Post into our UserWithPost struct:

struct UserWithPost {
  id: isize,
  post: Post,
}

struct Post {
  id: isize,
  text: String
}

Is there a way to make User generic over type of post field in such a way that allows us to write something like this (imaginary syntax):

fn process_users(users: Vec<User<Post = PostId>>) {}

fn process_users_harder(users: Vec<User<Post = Post>>) {}

I.e. I'd like a way to specify some concrete type for post field and use the concrete, instantiated type User<PostId> or User<Post>.

Solution with enum for post field won't fit since it doesn't restrict type of post field to any concrete type, only to a number of known types.

I've used a normal type parameter for something similar, but it's not exactly ergonomic if you have a bunch of foreign keys in a single struct.

struct User<T> {
  id: isize,
  post: T,
}

type PostId = isize;

struct Post {
  id: isize,
  text: String
}

let key_only: User<PostId> = todo!();
let with_post: User<Post> = todo!(); 

I usually do this with primary keys at least so there's a clear delineation between "saved" and "unsaved" structs, and it works pretty well in that context imo. (Unsaved is Table<()> saved is Table<TableId> and you can use Table<Option<TableId>> if you need to work with both)

Right, this totally works for a simple case like the one in OP.

But it doesn't prevent someone from instantiating User<Vec<usize>>, which doesn't make a lot of sense. Is there a way to restrict generic type to a handful of types? Making User<Post> and User<PostId> valid, but all other combinations invalid?

And I also would be interested in solution that can ergonomically handle many foreign keys in a single struct.

You can use a trait to restrict the valid types fairly easily.

As far as making things ergonomic when there are lots of foreign keys in a table I think you would probably need to fall back on macros.

You could either create a macro that simulates your User<Post = PostId> syntax or one that creates a new struct with the combination of types you want.

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.