I am trying to create a library which gets Values for multiple Tags from an SQL Database. Depending on the TagType which can be Analog or String - I need to return the matching value type. I tried to achieve this using a associated type named ValueType
in the Tag
trait
This is what i have got until now:
use sqlx::Row;
pub trait Tag {
type ValueType;
fn name(&self) -> String;
fn tagtype(&self) -> TagType;
}
pub enum TagType {
Analog = 1,
String = 3,
}
pub struct AnalogTag(String);
pub struct StringTag(String);
impl Tag for AnalogTag {
type ValueType = f64;
fn name(&self) -> String {
self.0.to_string()
}
fn tagtype(&self) -> TagType {
TagType::Analog
}
}
impl Tag for StringTag {
type ValueType = String;
fn name(&self) -> String {
self.0.to_string()
}
fn tagtype(&self) -> TagType {
TagType::String
}
}
pub struct Value<T> {
pub val: T,
pub quality: i8,
}
impl<T> Value<T> {
fn new(val: T, quality: i8) -> Self {
Self { val, quality }
}
}
pub async fn get_actual_value<T: Tag>(
db_pool: sqlx::MssqlPool,
tag: T,
) -> Result<Value<T::ValueType>, sqlx::Error> {
let table = match tag.tagtype() {
TagType::Analog => "AnalogLive",
TagType::String => "StringLive",
};
let result = sqlx::query("SELECT Value, Quality FROM @P1 WHERE Tagname = @P2")
.bind(table)
.bind(tag.name())
.fetch_one(&db_pool)
.await?;
let val = result.get("Value");
let quality: i8 = result.get("Quality");
Ok(Value::new(val, quality))
}
Anyhow, this will not work.
I need to implement sqlx::Decode
and Type<Mssql>
traits but don't know how this can be done for ValueType
that is an associated type of the trait Tag
the trait
sqlx::Decode<'_, Mssql>
is not implemented for<T as Tag>::ValueType
the traitType<Mssql>
is not implemented for<T as Tag>::ValueType
Any help would be appreciated!
EDIT Updated the code-example to a minimal reproducible example
CodePudding user response:
You are running into the problem here where you want to convert a type only known at runtime (the TagType
enum) into a type known at compile time (T::ValueType
). This is not trivial and requires some trickery. You have to understand that the compiler has zero knowledge about what tag.tagtype()
will return at compile time.
Luckily, the result::get()
function already has the hard work for that problem implemented in it.
So with a little bit of extra trait restrictions for T::ValueType
, you can get this to work:
use sqlx::Row;
pub trait Tag {
type ValueType;
fn name(&self) -> String;
fn tagtype(&self) -> TagType;
}
pub enum TagType {
Analog = 1,
String = 3,
}
pub struct AnalogTag(String);
pub struct StringTag(String);
impl Tag for AnalogTag {
type ValueType = f64;
fn name(&self) -> String {
self.0.to_string()
}
fn tagtype(&self) -> TagType {
TagType::Analog
}
}
impl Tag for StringTag {
type ValueType = String;
fn name(&self) -> String {
self.0.to_string()
}
fn tagtype(&self) -> TagType {
TagType::String
}
}
pub struct Value<T> {
pub val: T,
pub quality: i8,
}
impl<T> Value<T> {
fn new(val: T, quality: i8) -> Self {
Self { val, quality }
}
}
pub async fn get_actual_value<T>(
db_pool: sqlx::MssqlPool,
tag: T,
) -> Result<Value<T::ValueType>, sqlx::Error>
where
T: Tag,
for<'a> T::ValueType: sqlx::Decode<'a, sqlx::Mssql> sqlx::Type<sqlx::Mssql>,
{
let table = match tag.tagtype() {
TagType::Analog => "AnalogLive",
TagType::String => "StringLive",
};
let result = sqlx::query("SELECT Value, Quality FROM @P1 WHERE Tagname = @P2")
.bind(table)
.bind(tag.name())
.fetch_one(&db_pool)
.await?;
let quality: i8 = result.get("Quality");
let val: T::ValueType = result.get("Value");
Ok(Value::new(val, quality))
}