如何在Rust中为变量分配不同的类实例?

时间:2019-05-06 12:02:24

标签: rust

我正试图在Rust中创建一个简单的程序(出于教育目的)。现在,我主要使用Java之类的经典OOP语言进行开发,因此我意识到可能无法在Rust中实现相同的功能。

我试图通过根据外部(用户触发)输入初始化变量,然后在此对象实例上调用方法来避免重复代码。

我搜索了一会儿答案,但找不到我的问题的明确答案。

为了说明我的问题,我用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());
    }
}

这与我想要在Rust中实现的基本相同。据我了解,traits与Java中的interfaces等效。所以我尝试在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()
}

如果我现在正在尝试编译代码。我收到以下错误消息:

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

我尝试了相同的操作,但未明确定义cmd的类型。结果是

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

有人可以提示我如何在Rust中实现Java示例吗?

1 个答案:

答案 0 :(得分:4)

您的代码有几个问题。让我们按顺序整理它们。

首先,您不能将不同类型的值分配给变量。在Java中它之所以有效,是因为在Java中(几乎)所有东西都是堆分配的引用,但是Rust区分值和引用。因此,您需要明确告知编译器您需要堆分配的引用。在Rust中,这是通过Box完成的:

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

现在您遇到了第二个代码问题,即@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

在Java中,每个非静态方法都有一个称为this的隐式参数,该参数是对该方法应在其上运行的实例的引用。在Rust中,您需要将该引用显式声明为&self

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

这时您遇到了最后一个错误:代码末尾缺少分号。

最终工作代码:

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