1

I'm trying to create a simple program in Rust (for educational purposes). For now, I mostly developed in classic OOP languages like Java so I am aware that it might be that I cannot realize the same thing in Rust.

I am trying to avoid duplicate code by initializing a variable depending on an external (user triggered) input and then calling methods on this object instance.

I searched a while for an answer but I could not get a clear answer for my problem.

To specify my problem, I wrote the following lines in Java:

interface Command {
    String getName();
}

class FirstCommand implements Command {
    @Override
    public String getName() {
        return "First command";
    }
}

class SecondCommand implements Command {
    @Override
    public String getName() {
        return "Second command";
    }
}

public class Test {
    public static void main(String[] argv) {
        Command cmd;
        if (argv.length > 10) {
            cmd = new SecondCommand();
        } else {
            cmd = new FirstCommand();
        }
        System.out.println(cmd.getName());
    }
}

This is basically the same I want to achieve in Rust. As far as I understood, traits are the Rust equivalent to interfaces in Java. So I tried to do the same in Rust:

use std::env;

struct FirstCommand {}
struct SecondCommand {}

trait Command {
    fn get_name() -> &'static str;
}

impl FirstCommand {
    fn new() -> FirstCommand {
        FirstCommand {}
    }
}

impl Command for FirstCommand {
    fn get_name() -> &'static str {
        "First command"
    }
}

impl SecondCommand {
    fn new() -> SecondCommand {
        SecondCommand {}
    }
}

impl Command for SecondCommand {
    fn get_name() -> &'static str {
        "Second command"
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let cmd: Command = if args.len() > 10 {
        FirstCommand::new()
    } else {
        SecondCommand::new()
    };

    cmd.get_name()
}

If I am now trying to compile the code. I get the following error message:

38 |     let cmd: Command = if args.len() > 10 {
   |              ^^^^^^^ the trait `Command` cannot be made into an object

I tried the same without explicitly defining a type for cmd. This results in

38 |       let cmd = if args.len() > 10 {
   |  _______________-
39 | |         FirstCommand::new()
   | |         ------------------- expected because of this
40 | |     } else {
41 | |         SecondCommand::new()
   | |         ^^^^^^^^^^^^^^^^^^^^ expected struct `FirstCommand`, found struct `SecondCommand`
42 | |     };
   | |_____- if and else have incompatible types

Can someone give me a hint what to do to realize the Java example in Rust?

hellow
  • 12,430
  • 7
  • 56
  • 79
Tim
  • 65
  • 2
  • 6

1 Answers1

6

There are several issues with your code. Let's take them in order.

First you can't assign values of different types to a variable. In Java it works because in Java (almost) everything is a heap-allocated reference, but Rust distinguishes between values and references. So you need to tell the compiler explicitly that you want heap-allocated references. In Rust, this is done with Box:

let cmd: Box<dyn Command> = if args.len() > 10 {
    Box::new (FirstCommand::new())
} else {
    Box::new (SecondCommand::new())
};

Now you hit the second issue with your code, which is the duplicate pointed out by @French Boiethios:

error[E0038]: the trait `Command` cannot be made into an object
  --> src/main.rs:37:14
   |
37 |     let cmd: Box<dyn Command> = if args.len() > 10 {
   |              ^^^^^^^^^^^^^^^^ the trait `Command` cannot be made into an object
   |
   = note: method `get_name` has no receiver

In Java every non-static method has an implicit argument called this, which is a reference to the instance on which the method should work. In Rust you need to declare that reference explicitly as &self:

fn get_name (&self) -> &'static str;

At which point you hit the last error: there is a missing semicolon at the end of your code.

Final working code:

use std::env;

struct FirstCommand {}
struct SecondCommand {}

trait Command {
    fn get_name (&self) -> &'static str;
}

impl FirstCommand {
    fn new() -> FirstCommand {
        FirstCommand {}
    }
}

impl Command for FirstCommand {
    fn get_name (&self) -> &'static str {
        "First command"
    }
}

impl SecondCommand {
    fn new() -> SecondCommand {
        SecondCommand {}
    }
}

impl Command for SecondCommand {
    fn get_name (&self) -> &'static str {
        "Second command"
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let cmd: Box<dyn Command> = if args.len() > 10 {
        Box::new (FirstCommand::new())
    } else {
        Box::new (SecondCommand::new())
    };

    cmd.get_name();
}

playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • Thank you for your detailed and comprehensible answer. This helps me a lot. I guess I should also go back a bit al read more about the concept of Rust. Again, thank you! – Tim May 06 '19 at 12:52