簡體   English   中英

Rust 中 fn 參數的協方差使用什么?

[英]What to use for covariance over fn parameter in Rust?

今天我了解到 rust 不支持 fn 參數的協變,只有它的返回類型是協變的。 見 rust 文檔

為什么我會在 rust 中了解到這個事實? 因為我試圖實現一個非常簡單的游戲,其中我將邏輯、事件處理和繪圖分離為三個不同的函數,但它們都在同一個玩家向量上運行。

如果這是不可能的,那么與 c# 版本相比,rust 中的等效項是什么?

在 C# 這很簡單Fiddle您可以定義一個接口 Y,class X 必須實現並定義一個相應的委托,該委托需要該接口 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();
                }
            }
        );
    }
}

盡管我很天真,但我試圖做一些與 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(())
}

如果這是不可能的,那么與 c# 版本相比,rust 中的等效項是什么?

我接受這在 rust 中是不可能的。 我想知道在 rust 中使用的是什么

在 Rust 中,很少有事情是隱式完成的,包括您發現的強制轉換。

在這種情況下,將Vec<&T>轉換為Vec<&dyn Trait>是不可能的(鑒於T != dyn Trait ),因為 trait 對象是如何存儲的; 它們是兩個指針寬度,而普通引用是一個指針寬度。 這意味着Vec的長度(以字節為單位)需要加倍。

我接受這在 rust 中是不可能的。 我想知道在 rust 中使用的是什么

如果你只使用一種 object,你可以限制類型:

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

但是,您的游戲中很可能不僅有玩家,而是您希望包括其他方面。

這可以通過以下幾種方式完成:

  • 使用enum來表示 object 的每種類型。 然后您的 function 輸入可以采用enum類型的值:
enum GamePiece {
    Player(Player),
    Enemy(Enemy),
    Item(Item),
    //etc.
}
  • 使用可以根據對象具有的屬性管理任意對象的 ECS。 rust 中存在的一些 ECS 是:

    通常,它們的用法如下所示:

struct DrawingComponent {
    buffers: Buffer
}
struct DirectionAI {
    direction: Vector
}
struct Position {
    position: Point
}

let mut world = World::new();
world.insert((DrawingComponent::new(), DirectionAI::new(), Position::new()));

for (pos, direction) in world.iter_over(<(&mut Position, &DirectionAI)>::query()) {
    pos.position += direction.direction;
}
for (pos, drawable) in world.iter_over(<&Position, &mut DrawingComponent>::query()) {
    drawable.buffers.set_position(*pos);
    draw(drawable);
}

在這個系統中,您通常在組件上工作,而不是在類型上工作。 這樣,ECS 可以非常快速、高效地存儲和訪問項目。


Rust 中的協方差確實存在。 這不是 OOP 協方差,而是生命周期內的協方差。 Rust Nomicon 涵蓋了它,因為它對於日常用戶來說有點小眾。

請注意,該部分中的表格涵蓋了'aT以及在某些情況下U的方差。 TU的情況下,那里的方差存在於它們可能具有的任何生命周期參數中,而不是類型本身。 IE,它描述了'bStruct<'b>中如何變體(或不變),而不是如何將Struct<'b>強制轉換為dyn Trait + 'b

&Player視為&dyn Drawable看起來像是在超類型中使用子類型,但實際上它是一種類型轉換(兩者在 memory 中看起來完全不同 @Optimistic Peach 對此進行了更詳細的解釋)。

考慮到這一點, Vec<Player>不能被轉換成Vec<&dyn Drawable> ,它必須被轉換。 具有顯式轉換的代碼如下所示:

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

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

    for i in 0..1 {
        let actionable = objects.iter().map(|v| v as &dyn Actionable).collect();
        logic_handler(&actionable)?; // Does work!

        let drawables = objects.iter().map(|v| v as &dyn Drawable).collect();
        draw_handler(&drawables)?; // Does work!
    }

    Ok(())
}

這應該只演示將&Player轉換為&dyn Drawable的后果 - 這不是解決問題的最佳方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM