Hello
I have an application to manage cats. I can add cats to the system, delete them, view their info,...
My application is able to save these cats in memory or in a file. Because I used traits I can very easily choose if my application needs to save the info of the cats in memory or in a file.
Now I also want to be able to store the info of my cats in a MySQL Database. But a database of course requires more configuration than a memory-based or file-based system. My application needs to know the name of the database, username and password required to gain authentication.
But this is the problem I'm encountering:
Because memory- and file-based datastoring doesn't need much configuration a constructor-like function new() without parameters sufficed to initialize the structs FileData and MemoryData. This new()-function gets inherited from the generic trait.
But for MySQLData I'd like my new()-function to have parameters, like this: new(database_name : String, database_user : String, database_pass : String). However this is not allowed because new() is defined without parameters in the trait.
I was thinking of just overriding the required new()-function by simply adding a new()-function with parameters and let the other obligated new()-function without parameters return null. I got close but I am not able to let the new() function return null. Because it needs to return a MySQLData-object.
So basically:
How can I let a function with this signature: fn new() -> MySQLData
return a kind of null/nullptr/None so I don't have to implement it?
I wrote a small piece of pseudo-code to visualize my problem better.
Code:
//General interface for all the datamanagers
pub trait DataManager {
//Constructor
fn new() -> Self;
//Some basic functions to add/delete/... objects
fn add(&mut self, Object) -> bool;
fn delete(&mut self, ObjectId : u16) -> bool;
//...
}
//Keep objects stored in memory and manipulate them there
struct MemoryData {
//...
}
impl DataManager for MemoryData {
fn new() -> MemoryData {
MemoryData {
//...
}
}
}
//Keep objects stored in a file and manipulate them there
struct FileData {
//Same as MemoryData
//...
}
impl DataManager for MemoryData {
//Same as MemoryData
//...
}
//CODE FOR QUESTION STARTS HERE!!!!!!!!!
//Now I want to be able to also store objects in a database
extern crate mysql;
struct MySQLData {
conn : mysql::Pool, //Property holding the connection of the database
}
impl MySQLData {
//Function to initialize connection to MySQL database, returing mysql::Pool (Pooled connection)
pub fn init_connection(db_name : &String, db_user : &String, db_pass : &String) -> mysql::Pool {
//calling mysql::Pool::new() etc...
//Making connection using given parameters
//...
mysql_pool
}
//Function to create the tables needed for this application in the
//database if they don't already exist
fn init_tables(&mut self) {
//Executing few 'create table if not exists' queries
//...
}
//Trying to override the new() function obligated by DataManager
fn new(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
let mysql = MySQLData {
conn: MySQLData::init_connection(&String::from("catdatabase"), &String::from("root"), &String::from("toor")).unwrap(),
};
mysql.init_tables();
mysql
}
}
impl DataManager for MySQLData {
//This is the new() function obligated by DataManager
//However, I don't want to implement it! I want it to
//return a nullptr or an error when this one is used
fn new() -> MySQLData {
}
}
//Using the DataManagers
fn main() {
//Here I want to intialize a DataManager,
//because of the trait I am able to let my application switch between
//storing the data in memory (MemoryData) or in a file (FileData)
//by editing only this variable.
//Works: let ref mut manager = data_layer::MemoryData::new();
//Works: let ref mut manager = data_layer::FileData::new();
//What I want to do now:
//let ref mut manager = data_layer::MySQLData::new(); Should give some kind of error or do
//nothing
//I want this to work:
let ref mut manager = data_layer::MySQLData::new("database_name", "username", "password");
//I can execute the options from DataManager
manager.add(...);
manager.delete(...);
//...
}
Possible solutions I thought of myself but didn't like:
- Implementing both functions, and use default values in the function without parameters. But than I'd have almost the same code twice... Which I don't like.
- Changing the signature of new() -> Self, to new() -> Option or something close so I can return None in the obligated new() function. But than I'd have to change to much code in my application now (unwrapping etc).
- The ideal solution would be to be not implement the by the trait obligated new()-function but let it just return null.
Thanks!