Rust tips and tricks — enum Link to heading

enum is a powerful data type in Rust. It combines C++’s enum and variant into one. Say you are writing an interpreter, which supports literal types bool, i64, f64, and String. You can define an enum in Rust that holds one of the supported types and its value:

pub enum Literal {
    Bool(bool),
    Int(i64),
    Float(f64),
    String(String),
}

fn main() {
  let bool_true = Literal::Bool(true);
  let int_3 = Literal::Int(3);
  let float_pi = Literal::Float(3.14);
  let string_hello = Literal::String("hello".to_owned());
}

And you can do pattern-matching to do specific things based on the enum type:

fn print_type(literal: &Literal) {
    match literal {
        Literal::Bool(val) => println!("boolean: {}", val),
        Literal::Int(val) => println!("int: {}", val),
        Literal::Float(val) => println!("float: {}", val),
        Literal::String(val) => println!("string: {}", val),
    }
}

This is definitely much easier than C++’s visitor pattern for sure. OK, so far so good, but there is one thing that sort of bothers me. Every time I create a Literal, I need to explicitly write Literal::Bool(), Literal::Int(), etc. This is not as convenient as C++’s variant, where its variant type is automatically deduced:

#include <variant>
#include <string>

using Literal = std::variant<bool, int, float, std::string>;

int main() {
    Literal bool_true = true;
    Literal int_3 = 3;
    Literal float_pi = 3.14f;
    Literal string_hello = "hello";
}

Can we have our Literal deduce its type automatically as well? Of course, we can! There is a trait called From which handles the type conversion. That is, we implement From<T> trait for T=bool, i64, f64, and String so that they can be converted to Literal type. Heck, we can do this for &str type as well.

impl From<i64> for Literal {
    fn from(value: i64) -> Self {
        Literal::Int(value)
    }
}

impl From<f64> for Literal {
    fn from(value: f64) -> Self {
        Literal::Float(value)
    }
}

impl From<bool> for Literal {
    fn from(value: bool) -> Self {
        Literal::Bool(value)
    }
}

impl From<String> for Literal {
    fn from(value: String) -> Self {
        Literal::String(value)
    }
}

impl From<&str> for Literal {
    fn from(value: &str) -> Self {
        Literal::String(value.to_owned())
    }
}

Now, we can instantiate a Literal value using Literal::from() method:

--- before
+++ after
@@ -1,6 +1,6 @@
 fn main() {
-    let bool_true = Literal::Bool(true);
-    let int_3 = Literal::Int(3);
-    let float_pi = Literal::Float(3.14);
-    let string_hello = Literal::String("hello".to_owned());
+    let bool_true = Literal::from(true);
+    let int_3 = Literal::from(3);
+    let float_pi = Literal::from(3.14);
+    let string_hello = Literal::from("hello");
   }

Note that we didn’t even need to invoke "hello".to_owned() because we implemented From<&str> trait that converts to Literal::String type!