Can I create string enum? - rust

I am wondering is it possible to create enum with constant string values in Rust?
I found this previous question: How do I get an enum as a string? which shows a work around that I can use to stringify variants of an enum (I can use .to_string() on enum variants and get their name as a string).
That question was helpful, but this is what I want to achieve:
enum StringEnum {
Hello = "Hello",
World = "World"
}

If you have an enum like this:
enum HelloWorld {
Hello,
World
}
Hello and World are enum variants. Every variant of an enum is assigned to a single integer value. This is known as the discriminant. Currently, discriminants are only allowed to be integers, not arbitrary types like &'static str, although that may change in the future.
If you want to be able to convert your enum to a string, you can write a method that does this manually:
impl HelloWorld {
fn as_str(&self) -> &'static str {
match self {
HelloWorld::Hello => "Hello",
HelloWorld::World => "World"
}
}
}
Or you can have this done for you with the strum_macros crate:
#[derive(strum_macros::Display)]
pub enum StringEnum {
Hello,
World
}
fn main() {
let hello: &'static str = StringEnum::Hello.into(); // "Hello"
let world: String = StringEnum::World.to_string(); // "World"
}
There are a couple other methods mentioned in How do I get an enum as a string?

You can also have enum with data
pub enum TokenType<'a> {
STRING(&'a str),
}
and the this will give you the "test" value.
pub fn test() {
let token = TokenType::STRING("test");
if let TokenType::STRING(value) = token {
println!("{:?}", value)
}
}

Related

Can I declare an enum value that takes a String or &str without needing additional functions?

I have an enum with a String:
enum MyLovelyEnum {
Thing(String),
}
For tests, I would like to be able to pass in a &'static str to avoid MyLovelyEnum::Thing("abc".to_string) over and over.
I found that you can do this nicely with structs with a constructor:
// From: https://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html
struct Person {
name: String,
}
impl Person {
fn new<S: Into<String>>(name: S) -> Person {
Person { name: name.into() }
}
}
fn main() {
let person = Person::new("Herman");
let person = Person::new("Herman".to_string());
}
I know I can use lifetimes or Cow as described in What's the best practice for str/String values in Rust enums? or I can make my own function.
Is there something close to the example in the blog post for enums? e.g.
// this is the kind of thing I am after but this specifically is not correct syntax
enum MyLovelyEnum {
Thing<S: Into<String>>(S)
}
You can create a generic enum:
enum MyLovelyEnum<S>
where
S: Into<String>,
{
Thing(S),
}
MyLovelyEnum::Thing("a");
MyLovelyEnum::Thing("b".to_string());
I likely wouldn't do that in my code, instead opting to create a constructor, much like the blog post you linked:
enum MyLovelyEnum {
Thing(String),
}
impl MyLovelyEnum {
fn thing(s: impl Into<String>) -> Self {
MyLovelyEnum::Thing(s.into())
}
}
MyLovelyEnum::thing("a");
MyLovelyEnum::thing("b".to_string());

Get Data From Structs With String Key, And Struct to Array

Is it possible to get a value from a struct using a string key?
Like this:
struct Messages {
greetings: String,
goodbyes: String
}
impl Structure {
fn new() -> Structure{
Messages {
greetings: String::from("Hello world"),
goodbyes: String::from("Bye!")
}
}
fn main() {
let messages = Messages::new();
// now how do I print it out with a string key?
println!("{}",messages["greetings"]); // prints "Hello world"
// and can I even turn a struct into an array? Function name is made up
let arr = Struct::to_array(messages);
}
Pls help thz
In short, no, this is not possible. The names of your fields aren't necessarily available at runtime to perform such a check. However, there are other ways to achieve similar results:
use a HashMap<String, String>
write a function to do it:
impl MyStruct {
pub fn get_field(&self, key: String) -> &str {
if key == 'field1' {
self.field1
} else if ...
}
}
Write a derive macro to generate the above function automatically (https://crates.io/crates/field_names might be a useful starting point for how you might go about writing a derive macro)
Or solve it a different way
This pattern is not well supported in Rust, especially compared to more dynamic languages like JavaScript. A problem many new Rust learners face is solving problems "the Rust way".
It's not 100% clear from your comment, but it sounds like your struct is representing a tic-tac-toe board and looks something like this:
struct Board {
top_right: String,
top_middle: String,
top_left: String,
// etc...
}
While this works, there are much better ways of doing this. For example, you could represent each tile with an enum instead of a String, and you could also use an Vec (similar to arrays/lists from other languages) to store that data:
enum Tile {
Empty,
Cross,
Circle,
}
struct Board {
tiles: Vec<Tile>,
}
impl Board {
pub fn print(&self) {
for tile in self.tiles {
println!("{}", match tile {
Tile::Empty => " ",
Tile::Cross => "X"
Tile::Circle => "O",
});
}
}
}

Is it possible to reference enum variant of itself with another variant?

Is it possible to reference a variant of a enum, by another variant of the same enum in Rust?
Some imaginary code:
enum Message{
Text(String),
Secret(Self::Text)
}
Unfortunately, you can't. Message is a type, but Message::Text is not, and can't be used where types are used.
In this case the trivial solution is to just have Secret hold a String. However, assuming your real use case is a bit more complicated, the general strategy is to pull it out into another struct:
enum Message {
Text(MessageData),
Secret(MessageData),
}
struct MessageData {
foo: Bar,
// lots of other fields
}
Yes, it is possible! The Box type can wrap other types, allowing you to have self-referential types. This is needed in scenarios like linked lists.
I don't believe that you can specify that it wraps a specific variant though, so this is slightly different than what you asked for.
Here's an example with a little demonstration of hiding the secret!
use std::fmt;
enum Message {
Text(String),
Secret(Box<Self>)
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let contents = match self {
Message::Text(message) => message.to_string(),
Message::Secret(_) => "(hidden)".to_string(),
};
write!(f, "{}", contents)
}
}
fn main() {
let message = Message::Text("Hello world".to_string());
let secret = Message::Secret(Box::new(Message::Text("Don't read me!".to_string())));
println!("Message: {}", message);
println!("Secret: {}", secret);
}
Here's a playground link using the same code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=292286f473550a38fd24924dfafe7bfe

Enum to &str / &str to Enum

Wondering if there's a "proper" way of converting an Enum to a &str and back.
The problem I'm trying to solve:
In the clap crate, args/subcommands are defined and identified by &strs. (Which I'm assuming don't fully take advantage of the type checker.) I'd like to pass a Command Enum to my application instead of a &str which would be verified by the type-checker and also save me from typing (typo-ing?) strings all over the place.
This is what I came up with from searching StackOverflow and std:
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Command {
EatCake,
MakeCake,
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"eat-cake" => Ok(Self::EatCake),
"make-cake" => Ok(Self::MakeCake),
_ => Err(()),
}
}
}
impl<'a> From<Command> for &'a str {
fn from(c: Command) -> Self {
match c {
Command::EatCake => "eat-cake",
Command::MakeCake => "make-cake",
}
}
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
And here's a working playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b5e9ac450fd6a79b855306e96d4707fa
Here's an abridged version of what I'm running in clap.
let matches = App::new("cake")
.setting(AppSettings::SubcommandRequiredElseHelp)
// ...
.subcommand(
SubCommand::with_name(Command::MakeCake.into())
// ...
)
.subcommand(
SubCommand::with_name(Command::EatCake.into())
// ...
)
.get_matches();
It seems to work, but I'm not sure if I'm missing something / a bigger picture.
Related:
How to use an internal library Enum for Clap Args
How do I return an error within match statement while implementing from_str in rust?
Thanks!
The strum crate may save you some work. Using strum I was able to get the simple main() you have to work without any additional From implementations.
use strum_macros::{Display, EnumString, IntoStaticStr};
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Display, EnumString, IntoStaticStr)] // strum macros.
pub enum Command {
#[strum(serialize = "eat-cake")]
EatCake,
#[strum(serialize = "make-cake")]
MakeCake,
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}

How can I own a string through reference if its lifetime is static?

In Rust I would like to give a name to a class, and this class owns this name.
Sometimes the name is passed by an String. For this case I can just simply move the ownership.
But sometimes this name is given by a static string(&str).
For this case I want to refer to that string, rather than making a String from it.
My question is: how can I declare this name field in my class?
What type should it be?
Some updates/background on the requirements:
The name will be immutable.
The reason why I want to distinguish a String and a &str is that I want to reduce the dynamic allocation to the minimum.
One option is to declare the name member as an enum that can either contain a String or an &'static str:
enum Name {
Static(&'static str),
Owned(String),
}
struct Class {
name: Name,
// ...
}
The class can then provide appropriate constructors (there will have to be two) and a get_name() method for accessing the name as string slice:
impl Class {
pub fn new_from_str(name: &'static str) -> Class {
Class { name: Name::Static(name) }
}
pub fn new_from_owned(name: String) -> Class {
Class { name: Name::Owned(name) }
}
pub fn get_name(&self) -> &str {
match self.name {
Name::Owned(ref s) => s.as_str(),
Name::Static(s) => s,
}
}
}
fn main() {
let c1 = Class::new_from_str("foo");
let c2 = Class::new_from_owned("foo".to_string());
println!("{} {}", c1.get_name(), c2.get_name());
}
The other option is to use the Cow type provided by the standard library for this purpose:
use std::borrow::Cow;
struct Class {
name: Cow<'static, str>,
}
Since Cow implements the Into trait, the constructor can now be written as a single generic function:
pub fn new<T>(name: T) -> Class
where T: Into<Cow<'static, str>> {
Class { name: name.into() }
}
Cow also implements the Deref trait, allowing get_name() to be written as:
pub fn get_name(&self) -> &str {
return &self.name;
}
In both cases the name member will equal the size of the larger variant plus the space taken by the discriminator. As String is the larger type here, and it takes up three pointer sizes (the string contents is allocated separately and doesn't count), Name will take four pointer sizes in total. In case of explicit enum, the member can be made smaller still by boxing the string:
enum Name {
Static(&'static str),
Owned(Box<String>),
}
This will cut down the size of Name to three pointer sizes, of which one slot is used for the discriminator and the remaining two for the string slice. The downside is that it requires an additional allocation and indirection for the owned-string case - but it might still pay off if the majority of your class names come from static string slices.
What you can do to allow for a mix of types is use the Into trait. This is versatile in that it makes sure a safe conversion happens between the types.
A str slice can be converted "into" an owned String as such.
Some test code to demonstrate it:
#[derive(Debug)]
struct Test {
name: String,
}
impl Test {
pub fn new<T: Into<String>>(t: T) -> Test {
Test { name: t.into() }
}
}
fn main() {
let t = Test::new("a");
println!("{:?}", t);
}

Resources