Const generics for other Types

Hi y'all,
I want to make my serde code more generic over a &'static str but apperantly I can't use that as a const generic parameter. My goal is to use it in something like

#[serde(with = "Validator<\"2.0\">")]

So I don't have to copy-paste my current code

mod jsonrpc_version {
    use std::fmt::Formatter;
    use serde::{Deserializer, Serializer};
    use serde::de::{Error, Unexpected, Visitor};

    const EXPECTED_VALUE: &'static str = "2.0"

    pub(crate) fn serialize<S>(_version: &(), serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
        serializer.serialize_str(EXPECTED_VALUE)
    }

    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> where D: Deserializer<'de> {
        deserializer.deserialize_str(VersionVisitor)
    }

    struct VersionVisitor;

    impl<'de> Visitor<'de> for VersionVisitor {
        type Value = ();

        fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
            formatter.write_str(EXPECTED_VALUE)
        }

        fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where E: Error {
            if v == EXPECTED_VALUE {
                Ok(())
            } else {
                Err(E::invalid_value(Unexpected::Str(v), &EXPECTED_VALUE))
            }
        }
    }
}

Is there something I can do or is this a limitation that I can't overcome?

Make your validator generic over a type instead, and implement a trait for that (set of) type(s), in which you define an associated const &str with the appropriate value.

1 Like

That brings me a step further, thanks. Now I need only to figure out how to pass that to the Visitor.

Thanks, your idea was the part I was still missing. I didn't know that was possible in Rust. Here's my result for anyone wondering:

use std::fmt::Formatter;
use serde::{Deserializer, Serializer};
use serde::de::{Error, Unexpected, Visitor};

pub trait Validator {
    const EXPECTED_VALUE: &'static str;

    fn serialize<S>(_version: &(), serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
        serializer.serialize_str(Self::EXPECTED_VALUE)
    }

    fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> where D: Deserializer<'de> {
        deserializer.deserialize_str(ValidatorVisitor {expected: Self::EXPECTED_VALUE})
    }
}

struct ValidatorVisitor {
    expected: &'static str,
}

impl<'de> Visitor<'de> for ValidatorVisitor {
    type Value = ();

    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
        formatter.write_str(self.expected)
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where E: Error {
        if v == self.expected {
            Ok(())
        } else {
            Err(E::invalid_value(Unexpected::Str(v), &self.expected))
        }
    }
}

Have you tried with #[serde(with = "Validator::<\"2.0\">")] (i.e. with turbofish syntax)?

Hi, the problem wasn't the call, but the implementation to use it. But thanks for your input :wink: I solved it now and posted the result in another comment here.