Что использовать для ковариации параметра fn в Rust?

сегодня я узнал, что ржавчина не поддерживает ковариантность для параметра fn, только его возвращаемый тип является ковариантным. (см. документацию по rust)

Почему я узнал об этом факте в ржавчине? Потому что я попытался реализовать очень простую игру, в которой я разделил логику, обработку событий и рисование на три отдельные функции, но все они работают с одним и тем же вектором игроков.

Если это невозможно, что будет эквивалентно в Rust по сравнению с версией C#?

В C# это довольно просто Fiddle. Вы можете определить интерфейс Y, который должен реализовать класс X, и определить соответствующий делегат, который требует в качестве параметра IEnumerable этого интерфейса Y. Теперь вы можете совместно использовать список 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();
                }
            }
        );
    }
}

Как бы я ни был наивен, я пытался сделать что-то похожее на ржавчину!

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(())
}

Если это невозможно, что будет эквивалентно в Rust по сравнению с версией C#?

Я согласен, что это невозможно в ржавчине. Я хотел бы знать, что вместо этого используется в ржавчине


person ExOfDe    schedule 11.06.2020    source источник
comment
Вы можете посмотреть на github.com/amethyst/amethyst, но этот вопрос слишком широк для SO вопрос, и я не буду называть ваш код простым, наоборот, я бы посоветовал вам сделать его простым, не пытайтесь делать так много абстракций, прежде чем они вам понадобятся.   -  person Stargateur    schedule 12.06.2020
comment
@Stargateur спасибо за ссылку. Вы уверены, что это слишком широко для SO, потому что, если я увижу такие вопросы, как stackoverflow.com/questions/141088/ Я предположил, что этот подойдет.   -  person ExOfDe    schedule 12.06.2020
comment
Я не вижу связи между базовым вопросом, таким как повторение коллекции, и вашим вопросом, для ответа на который потребуется книга. Вы просто выбираете случайный вопрос C#.   -  person Stargateur    schedule 12.06.2020
comment
Действительно я сделал. Я думаю, что мне не хватает навыков, чтобы увидеть разветвления моего вопроса.   -  person ExOfDe    schedule 12.06.2020


Ответы (2)


В Rust очень мало вещей делается неявно, включая приведение типов, как вы обнаружили.

В этом случае преобразование Vec<&T> в Vec<&dyn Trait> было бы невозможно (учитывая, что T != dyn Trait) из-за того, как хранятся трейт-объекты; они имеют ширину в два указателя, в то время как обычные ссылки имеют ширину в один указатель. Это означает, что длина Vec в байтах должна быть удвоена.

Я согласен, что это невозможно в ржавчине. Я хотел бы знать, что вместо этого используется в ржавчине

Если вы используете только один тип объекта, вы можете просто ограничить тип:

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

Однако, скорее всего, в вашей игре будут не только игроки, вместо этого вы хотели бы включить другие аспекты.

Это можно сделать несколькими способами:

  • Использование enums для представления каждого типа объекта. Тогда ваши входные данные функции могут принимать значения типа enum:
enum GamePiece {
    Player(Player),
    Enemy(Enemy),
    Item(Item),
    //etc.
}
  • Использование ECS, которая может управлять произвольными объектами в зависимости от их свойств. Некоторые 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 действительно существует. Это просто не ковариация ООП, это ковариация по времени жизни. Rust Nomicon покрывает это, так как это немного нишевая вещь для повседневных пользователей.

Обратите внимание, что таблица в этом разделе охватывает дисперсию 'a, T и, в некоторых случаях, U. В случае T и U разница заключается в любых параметрах срока службы, которые они могут иметь, а не в самом типе. IE, он описывает, как 'b является вариантом (или инвариантом) в Struct<'b>, а не как Struct<'b> можно преобразовать в dyn Trait + 'b.

person Optimistic Peach    schedule 11.06.2020

Обработка &Player как &dyn Drawable выглядит как использование подтипа в супертипе, но на самом деле это преобразование типа (в памяти оба выглядят совершенно по-разному, @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 - это не лучший способ решить вашу проблему.

person CoronA    schedule 13.06.2020