You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@thrift.apache.org by "Samuele Maci (Jira)" <ji...@apache.org> on 2021/04/08 10:58:00 UTC

[jira] [Updated] (THRIFT-5392) Thrift Enums should generate enum like code

     [ https://issues.apache.org/jira/browse/THRIFT-5392?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Samuele Maci updated THRIFT-5392:
---------------------------------
    Component/s: Rust - Library

> Thrift Enums should generate enum like code
> -------------------------------------------
>
>                 Key: THRIFT-5392
>                 URL: https://issues.apache.org/jira/browse/THRIFT-5392
>             Project: Thrift
>          Issue Type: Improvement
>          Components: Rust - Compiler, Rust - Library
>            Reporter: Samuele Maci
>            Priority: Major
>              Labels: improvement, rust
>
> I'm starting using thrift interfaces in rust and I've been surprised to discover that thrift enums are not generated as rust enums.
> The following thrift enum
> {code:java}
> # Fully made up enum to use as example
> enum HttpStatusCode {
>   Ok = 200,
>   Created = 201,
>   Accepted = 202,
> } 
> {code}
> is currently rendered as
> {code:java}
> // The generated code has been shrank a bit for readability
> #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
> pub struct HttpStatusCode(pub i32);
> impl HttpStatusCode {
>     pub const Ok: Self = HttpStatusCode(200);
>     pub const Created: Self = HttpStatusCode(201);
>     pub const Accepted: Self = HttpStatusCode(202);
> }
> impl ::fbthrift::ThriftEnum for HttpStatusCode {
>     fn enumerate() -> &'static [(HttpStatusCode, &'static str)] {
>         &[(Self::Ok, "Ok"), (Self::Created, "Created"), (Self::Accepted, "Accepted")]
>     }
>     fn variants() -> &'static [&'static str] {
>         &["Ok", "Created", "Accepted"]
>     }
>     fn variant_values() -> &'static [HttpStatusCode] {
>         &[Self::Ok, Self::Created, Self::Accepted]
>     }
> }
> impl Default for HttpStatusCode {
>     fn default() -> Self { HttpStatusCode(::fbthrift::__UNKNOWN_ID) }
> }
> impl<'a> From<&'a HttpStatusCode> for i32 {
>     fn from(x: &'a HttpStatusCode) -> Self { x.0 }
> }
> impl From<HttpStatusCode> for i32 {
>     fn from(x: HttpStatusCode) -> Self { x.0 }
> }
> impl From<i32> for HttpStatusCode {
>     fn from(x: i32) -> Self { Self(x) }
> }
> {code}
> The generated code poses, in my view, some limitations as well as it does not use the powerful rust compiler capabilities:
>  * having a struct instead of enum removes the capability of the compiler to verify for exhaustive matching. The code below would compile
> {code:java}
> let enum_value: HttpStatusCode = foo();
> match enum_value {
>   HttpStatusCode::Ok => do_ok(),
>   HttpStatusCode::Created => do_ok(),
>   // HttpStatusCode::Accepted => ...  // This is intentionally left out
> }{code}
>  * we allow creating un-existing enums from code
> {code:java}
> let enum_value = HttpStatusCode(1234);  // I would have expected an error{code}
> I would have expected that TryFrom<i32> was implemented and not the infallible form From<i32>
>  * we do not allow creating enums from variant names (but we do it from enum "binary" value)
> {code:java}
> let enum_value = HttpStatusCode::try_from("Ok")?;  // I would have expected to be possible{code}
> I do see that the conversion from rust enum to rust struct was done in THRIFT-5314 for forward-compatibility but I'm wondering if that is the best way forward to ensure that we can levarage what the rust ecosystem can provide us.
>  This is mostly meant to lift developers from some tedious and error-prone checks which the compiler knows how to do.
>  NOTE: Working of a wide code-base and depending on thrift definition of thrid part services makes very hard to keep track of all the changes and having the compiler reporting the issues is a non-negligible advantage.
> *How could we move forward without impacting way too much on the current experience?*
>  My idea would be to have back enum variants and in order to have backward/forward capabilities I would suggest the introduction of a sentinel enum variant (as using Result<HttpStatusCode, ::fbthrift::Error> does not look appealing to most)
> {code:java}
> #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
> pub enum HttpStatusCode {
>   Unknown,  // Maybe the variant name could be configurable
>   Ok, Created, Accepted,
> }
> impl ::fbthrift::ThriftEnum for HttpStatusCode {
>     // As current 
>     ...
> }
> impl Default for HttpStatusCode {
>     fn default() -> Self { Self::Unknown }
> }
> impl<'a> From<&'a HttpStatusCode> for i32 {
>     fn from(x: &'a HttpStatusCode) -> Self {
>         match x {
>             HttpStatusCode::Unknown => ::fbthrift::__UNKNOWN_ID,
>             HttpStatusCode::Ok => 200,
>             HttpStatusCode::Created => 201,
>             HttpStatusCode::Accepted => 202,
>         }
>     }
> }
> impl From<HttpStatusCode> for i32 {
>     fn from(x: HttpStatusCode) -> Self { Self::from(&x) }
> }
> impl TryFrom<i32> for HttpStatusCode {
>     type Error = ::fbthrift::ProtocolError;
>     fn try_from(x: i32) -> Result<Self, Self::Error> {
>         match x {
>             200 => Ok(Self::Ok),
>             201 => Ok(Self::Created),
>             202 => Ok(Self::Accepted),
>             _ => Err(::fbthrift::ProtocolError::InvalidValue),
>         }
>     }
> }
> impl HttpStatusCode {
>     // Not From trait because it conflicts with From implementation from TryFrom
>     fn from(x: i32) -> Self {
>         Self::try_from(x).unwrap_or_default()
>     }
> }
> impl<'a> std::convert::TryFrom<&'a str> for HttpStatusCode {
>     type Error = ::fbthrift::ProtocolError;
>     fn try_from(x: &'a str) -> Result<Self, Self::Error> {
>         match x {
>             "Ok" => Ok(Self::Ok),
>             "Created" => Ok(Self::Created),
>             "Accepted" => Ok(Self::Accepted),
>             _ => Err(::fbthrift::ProtocolError::InvalidValue),
>         }
>     }
> }
> #[test]
> fn dummy_test() {
>     assert_eq!(HttpStatusCode::Ok, HttpStatusCode::from(200));
>     assert_eq!(HttpStatusCode::Unknown, HttpStatusCode::from(300));
>     assert!(HttpStatusCode::try_from(200).is_ok());
>     assert!(HttpStatusCode::try_from(300).is_err());
> }
> {code}
> Before eventually moving forward with updating the compiler to support the new schema (and I can be doing so) I would like to have a sort of discussion in order to avoid investing time toward a non-acceptable/non-ideal direction.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)