Reputation: 14505
I'm struggling with how to safely cast an argument and make sure all possibilities are handled.
As I have a Kotlin background, I'm writing samples in that language:
package question
class StateA(val message: String)
class StateB(val number: Int)
object StateC
fun <State> render(state: State) = when (state) {
is StateA -> println("state is StateA, its message: ${state.message}")
is StateB -> println("state is StateB, its number: ${state.number}")
StateC -> println("state is StateC")
else -> println("Unknown type")
}
fun main() {
render(StateA("Hello world")) // prints: state is StateA, its message: Hello world
render(StateB(42)) // prints: state is StateB, its number: 42
render(StateC) // prints: state is StateC
render("Other type") // prints: Unknown type
}
The when
statement checks the generic type State
and makes a safe cast when the type matches so I can access message
if the type is StateA
or I can access number
if the state is an instance of StateB
.
That code has an issue, the else branch, but I can get rid of it by using a language feature called sealed class and with that I know all possible types at compile time:
package question
import question.State.*
sealed class State {
class StateA(val message: String) : State()
class StateB(val number: Int) : State()
object StateC : State()
}
fun render(state: State) = when (state) {
is StateA -> println("state is StateA, its message: ${state.message}")
is StateB -> println("state is StateB, its number: ${state.number}")
StateC -> println("state is StateC")
}
fun main() {
render(StateA("Hello world")) // prints: state is StateA, its message: Hello world
render(StateB(42)) // prints: state is StateB, its number: 42
render(StateC) // prints: state is StateC
}
The closest I got in Rust was using Any
:
use std::any::Any;
struct StateA {
message: String,
}
struct StateB {
number: u8,
}
struct StateC {}
fn render(state: &dyn Any) {
if let Some(state_a) = state.downcast_ref::<StateA>() {
println!("state is StateA, its message: {}", state_a.message);
} else if let Some(state_b) = state.downcast_ref::<StateB>() {
println!("state is StateB, its number: {}", state_b.number);
} else if let Some(_) = state.downcast_ref::<StateC>() {
println!("state is StateC");
} else {
println!("Unknown type")
}
}
fn main() {
render(&StateA {
message: String::from("Hello World"),
}); // prints: state is StateA, its message: Hello World
render(&StateB { number: 42 }); // prints: state is StateB, its number: 42
render(&StateC {}); // prints: state is StateC
render(&"Other type"); // prints: Unknown type
}
This solution is similar to the first one in Kotlin, so it has the same issue, the else branch
Is there any way to have a safe cast knowing all possible types at compile time? Something similar to Kotlin's sealed class solution, and get rid of the else branch?
Upvotes: 0
Views: 48
Reputation: 14505
Using Shepmaster's tip to use enums, I managed to solve the issue.
The working code:
enum State {
StateA { message: String },
StateB { number: u8 },
StateC,
}
fn render(state: &State) {
match state {
State::StateA { message } => println!("state is StateA, its message: {}", message),
State::StateB { number } => println!("state is StateB, its number: {}", number),
State::StateC => println!("state is StateC"),
}
}
fn main() {
render(&State::StateA {
message: String::from("Hello World"),
}); // prints: state is StateA, its message: Hello World
render(&State::StateB { number: 42 }); // prints: state is StateB, its number: 42
render(&State::StateC {}); // prints: state is StateC
}
Upvotes: 2