Rust 中 fn 参数的协方差使用什么?

ExO*_*fDe 3 c# covariance rust

今天我了解到 rust 不支持 fn 参数的协变,只有它的返回类型是协变的。(参见 Rust 文档

为什么我在 Rust 中了解到这个事实?因为我试图实现一个非常简单的游戏,我将逻辑、事件处理和绘图分离为三个不同的函数,但所有函数都在相同的玩家向量上运行。

如果这是不可能的,与 C# 版本相比,Rust 中的等效项是什么?

在 C# 中,这是非常简单的Fiddle 您可以定义一个类 X 必须实现的接口 Y,并定义一个相应的委托,该委托需要该接口 Y 的 IEnumerable 作为参数。现在您可以在需要的不同方法之间共享 X 的列表只有一个接口Y。

using System;
using System.Collections.Generic;


public interface Actionable{
    void Do();
}

public interface Drawable{
    void Draw();
}

public class Player: Drawable, Actionable{

    public void Do(){
        Console.WriteLine("Action");
    }

    public void Draw(){
        Console.WriteLine("Draw");
    }
}

public class Program
{
    public delegate void DrawHandler(IEnumerable<Drawable> obj);
    public delegate void LogicHandler(IEnumerable<Actionable> obj);

    public static void gameloop(DrawHandler draw,LogicHandler action){

        List<Player> list = new List<Player>(){
            new Player()
        };

        for(int rounds = 0; rounds < 500; rounds++){
            draw(list);
            action(list);
        }

    }
    public static void Main()
    {
        gameloop(
             list =>{
                foreach(var item in list){
                    item.Draw();
                }
            },
            list =>{
                foreach(var item in list){
                    item.Do();
                }
            }
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管我很天真,但我尝试做一些与 Rust 相同的事情!

trait Drawable {
    fn draw(&self) {
        println!("draw object");
    }
}

trait Actionable {
    fn do_action(&self, action: &String) {
        println!("Do {}", action);
    }
}

#[derive(Debug)]
struct Position {
    x: u32,
    y: u32,
}
impl Position {
    fn new(x: u32, y: u32) -> Position {
        Position { x, y }
    }
}
#[derive(Debug)]
struct Player {
    pos: Position,
    name: String,
}

impl Player {
    fn new(name: String) -> Player {
        Player {
            name,
            pos: Position::new(0, 0),
        }
    }
}

impl Drawable for Player {
    fn draw(&self) {
        println!("{:?}", self);
    }
}

impl Actionable for Player {
    fn do_action(&self, action: &String) {
        println!("Do {} {}!", action, self.name);
    }
}

type DrawHandler = fn(drawables: &Vec<&dyn Drawable>) -> Result<(), String>;
type LogicHandler = fn(actions: &Vec<&dyn Actionable>) -> Result<(), String>;
type EventHandler = fn(events: &mut sdl2::EventPump) -> Result<bool, String>;

fn game_loop(
    window: &mut windowContext,
    draw_handler: DrawHandler,
    event_handler: EventHandler,
    logic_handler: LogicHandler,
) -> Result<(), String> {
    let mut objects: Vec<&Player> = Vec::new();

    objects.push(&Player::new("b".to_string()));

    while event_handler(&mut window.events)? {
        logic_handler(&objects)?; // Does Not work

        window.canvas.clear();

        draw_handler(&objects)?; // Does Not Work
        window.canvas.present();
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }

    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

如果这是不可能的,与 C# 版本相比,Rust 中的等效项是什么?

我承认这在 Rust 中是不可能的。我想知道 Rust 中用什么代替

Opt*_*ach 5

在 Rust 中,很少有事情是隐式完成的,其中包括您发现的强制转换。

在这种情况下,由于特征对象的存储方式,将 a 转换Vec<&T>为 aVec<&dyn Trait>是不可能的(鉴于);T != dyn Trait它们的宽度是两个指针宽度,而普通引用的宽度是一个指针宽度。这意味着 , 的长度Vec(以字节为单位)需要加倍。

我承认这在 Rust 中是不可能的。我想知道 Rust 中用什么代替

如果您只使用一种对象,则可以限制类型:

type DrawHandler = fn(drawables: &Vec<Player>) -> Result<(), String>;
type LogicHandler = fn(actions: &Vec<Player>) -> Result<(), String>;
Run Code Online (Sandbox Code Playgroud)

然而,您的游戏中很可能不仅有玩家,而且您还希望包括其他方面。

这可以通过几种方式完成:

  • 使用enums 来表示每种类型的对象。然后您的函数输入可以采用以下类型的值enum
type DrawHandler = fn(drawables: &Vec<Player>) -> Result<(), String>;
type LogicHandler = fn(actions: &Vec<Player>) -> Result<(), String>;
Run Code Online (Sandbox Code Playgroud)
  • 使用 ECS 可以根据对象的属性来管理任意对象。Rust中存在的一些ECS是:

    一般来说,它们的用法如下:

enum GamePiece {
    Player(Player),
    Enemy(Enemy),
    Item(Item),
    //etc.
}
Run Code Online (Sandbox Code Playgroud)

在此系统中,您通常在组件上工作,而不是在类型上工作。这样,ECS 就可以非常快速、高效地存储和访问项目。


Rust 中确实存在协方差。它不是 OOP 协方差,而是整个生命周期的协方差。Rust Nomicon 涵盖了它,因为它对于日常用户来说有点小众。

请注意,该部分中的表涵盖了'aT以及在某些情况下的方差UT在、 和 的情况下U,差异存在于它们可能具有的任何生命周期参数中,而不是类型本身。IE,它描述了如何'b在 中变体(或不变)Struct<'b>,而不是如何Struct<'b>转换为dyn Trait + 'b