This topic follows a previous one where I was fighting with the bindings generation for an extern C library loaded at runtime.
I want to offer the opportunity to handle various versions ... at runtime.
I have multiple bindings_vX.X.X.rs
files generated at build time.
And I've a struct loading and storing the library and the version number, which should expose the Rust version of the functions in the C library.
I wonder how I can use "I don't know what" (traits ? other things ?) to let the user do something like :
let library_v1 = MyLibrary::new("/path/to/library/v1", SupportedVersions::V1);
let library_v2 = MyLibrary::new("/path/to/library/v2", SupportedVersions::V2);
// V1 & V2 functions doesn't always have the same signatures
let result: u16 = library_v1.do_something("arg1", 12);
let result: String = library_v2.do_something(12, "arg1", false);
Any idea ?
I can think of 3 approaches:
- a Individual library struct for each library version
- prefixes/suffixes on each function version which changed in a newer version
- use some kind of generics:
I think option 3 is probably the nicest, so let me draft a quick example on how you could go about it:
// avoid people implementing the LibVersion trait outside your crate
mod sealed { pub trait Sealed {} }
pub trait LibVersion: sealed::Sealed { }
struct Version1;
impl sealed::Sealed for Version1 {}
impl LibVersion for Version1 {}
struct Version2;
impl sealed::Sealed for Version2 {}
impl LibVersion for Version2 {}
struct MyLibrary<Version: LibVersion> {
// ...
}
// think of a better name plz
pub(crate) trait SharedFeaturesVersion1And2 {}
impl SharedFeaturesVersion1And2 for Version1 {}
impl SharedFeaturesVersion1And2 for Version2 {}
// signature only in version 1
impl MyLibrary<Version1> {
fn foo(arg1: bool) { /* ...*/ }
}
// signature only in version 2
impl MyLibrary<Version2> {
fn foo(arg1: bool, arg2: const * c_str) { /* ...*/ }
}
// signature shared between version 1 and 2
impl<T: SharedFeaturesVersion1And2> MyLibrary<T> {
fn bar(arg1: bool, arg2: const * c_str, arg3: u16) { /* ...*/ }
}
// usage:
let my_lib_v1 = MyLibrary::<Version1>::new(...);
let my_lib_v2 = MyLibrary::<Version2>::new(...);
// different functions
my_lib_v1.foo(...);
my_lib_v2.foo(...,...);
// same function called
my_lib_v1.bar(...,...,...);
my_lib_v2.bar(...,...,...);
this approach is similar to the typestate pattern often used for builders, etc.
1 Like
Thanks for your answer, it helps me so much !!!
Here is the full example, with details showing how/where I've implemented the ::new constructor initializing the library :
use std::marker::PhantomData;
mod sealed { pub trait Sealed {} }
struct FakeLibrary {
value: i32,
}
impl FakeLibrary {
fn add_ten(&self) -> i32 {
self.value + 10
}
}
trait MyLibCommon {
fn new(init_value: i32) -> Result<Self, String> where Self: Sized;
fn get_version_code(&self) -> &'static str;
}
trait MyLibVersion: sealed::Sealed {
fn get_version_code() -> &'static str;
}
struct V1;
impl sealed::Sealed for V1 {}
impl MyLibVersion for V1 {
fn get_version_code() -> &'static str {
"v1"
}
}
struct V2;
impl sealed::Sealed for V2 {}
impl MyLibVersion for V2 {
fn get_version_code() -> &'static str {
"v2"
}
}
struct MyLib<Version: MyLibVersion> {
library: FakeLibrary,
_phantom: PhantomData<Version>,
}
impl<Version: MyLibVersion> MyLibCommon for MyLib<Version> {
fn new(init_value: i32) -> Result<Self, String> {
Ok(MyLib {
library: FakeLibrary { value: init_value },
_phantom: PhantomData,
})
}
fn get_version_code(&self) -> &'static str {
Version::get_version_code()
}
}
impl MyLib<V1> {
fn add_twenty_v1(&self) -> i32 {
self.library.add_ten() + 10
}
}
impl MyLib<V2> {
fn add_twenty_v2(&self) -> String {
let result = self.library.add_ten() + 10;
result.to_string()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn init_mylib_v1_v2() {
let _lib_v1 = MyLib::<V1> {
library: FakeLibrary { value: 12 },
_phantom: PhantomData,
};
let _lib_v2 = MyLib::<V2> {
library: FakeLibrary { value: 18 },
_phantom: PhantomData,
};
}
#[test]
fn get_version_code() {
let lib_v1 = MyLib::<V1> {
library: FakeLibrary { value: 12 },
_phantom: PhantomData,
};
let lib_v2 = MyLib::<V2> {
library: FakeLibrary { value: 18 },
_phantom: PhantomData,
};
assert_eq!(lib_v1.get_version_code(), "v1");
assert_eq!(lib_v2.get_version_code(), "v2");
}
#[test]
fn use_new_contructor() {
let lib_v1 = MyLib::<V1>::new(18).unwrap();
let lib_v2 = MyLib::<V2>::new(72).unwrap();
}
#[test]
fn run_library() {
let lib_v1 = MyLib::<V1>::new(18).unwrap();
let lib_v2 = MyLib::<V2>::new(72).unwrap();
assert_eq!(lib_v1.library.add_ten(), 28);
assert_eq!(lib_v2.library.add_ten(), 82);
}
#[test]
fn run_library_v1() {
let lib_v1 = MyLib::<V1>::new(18).unwrap();
assert_eq!(lib_v1.add_twenty_v1(), 38);
}
#[test]
fn run_library_v2() {
let lib_v2 = MyLib::<V2>::new(72).unwrap();
assert_eq!(lib_v2.add_twenty_v2(), "92");
}
}