Rust 中 fn 参数的协方差使用什么?
What to use for covariance over fn parameter in Rust?
今天我了解到 Rust 不支持 fn 参数的协变,只有它的 return 类型是协变的。 (see rust doc)
为什么我在 rust 中了解到这个事实?因为我试图实现一个非常简单的游戏,我将逻辑、事件处理和绘图分成三个不同的函数,但所有函数都在相同的玩家向量上运行。
如果这不可能,与 c# 版本相比,rust 的等价物是什么?
在 C# 中,这非常简单 Fiddle
您可以定义一个 class 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();
}
}
);
}
}
虽然我很天真,但我尝试做一些与 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 中,很少有事情是隐式完成的,这包括您发现的转换。
在这种情况下,由于 trait 对象的存储方式,将 Vec<&T>
转换为 Vec<&dyn Trait>
是不可能的(鉴于 T != dyn Trait
);它们是两个指针宽度,而普通引用是一个指针宽度。这意味着 Vec
的长度(以字节为单位)需要加倍。
I accept that this is not possible in rust. I would like to know what instead is used in rust
如果你只使用一种对象,你可以只限制类型:
type DrawHandler = fn(drawables: &Vec<Player>) -> Result<(), String>;
type LogicHandler = fn(actions: &Vec<Player>) -> Result<(), String>;
但是,很可能您的游戏中不仅有玩家,而且您还想包括其他方面。
这可以通过几种方式完成:
- 用
enum
s来表示每一类对象。然后您的函数输入可以采用 enum
: 类型的值
enum GamePiece {
Player(Player),
Enemy(Enemy),
Item(Item),
//etc.
}
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 协方差,它是整个生命周期的协方差。 The Rust Nomicon covers it,因为它对日常用户来说有点小众。
请注意,该部分中的 table 涵盖 'a
、T
的方差,在某些情况下,U
。在 T
和 U
的情况下,那里的差异在于它们可能具有的任何生命周期参数,而不是类型本身。 IE,它描述了 'b
如何在 Struct<'b>
中变体(或不变),而不是如何将 Struct<'b>
转换为 dyn Trait + 'b
。
Treating a &Player
as &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
的结果 - 这不是解决问题的最佳方法。
今天我了解到 Rust 不支持 fn 参数的协变,只有它的 return 类型是协变的。 (see rust doc)
为什么我在 rust 中了解到这个事实?因为我试图实现一个非常简单的游戏,我将逻辑、事件处理和绘图分成三个不同的函数,但所有函数都在相同的玩家向量上运行。
如果这不可能,与 c# 版本相比,rust 的等价物是什么?
在 C# 中,这非常简单 Fiddle 您可以定义一个 class 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();
}
}
);
}
}
虽然我很天真,但我尝试做一些与 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 中,很少有事情是隐式完成的,这包括您发现的转换。
在这种情况下,由于 trait 对象的存储方式,将 Vec<&T>
转换为 Vec<&dyn Trait>
是不可能的(鉴于 T != dyn Trait
);它们是两个指针宽度,而普通引用是一个指针宽度。这意味着 Vec
的长度(以字节为单位)需要加倍。
I accept that this is not possible in rust. I would like to know what instead is used in rust
如果你只使用一种对象,你可以只限制类型:
type DrawHandler = fn(drawables: &Vec<Player>) -> Result<(), String>;
type LogicHandler = fn(actions: &Vec<Player>) -> Result<(), String>;
但是,很可能您的游戏中不仅有玩家,而且您还想包括其他方面。
这可以通过几种方式完成:
- 用
enum
s来表示每一类对象。然后您的函数输入可以采用enum
: 类型的值
enum GamePiece {
Player(Player),
Enemy(Enemy),
Item(Item),
//etc.
}
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 协方差,它是整个生命周期的协方差。 The Rust Nomicon covers it,因为它对日常用户来说有点小众。
请注意,该部分中的 table 涵盖 'a
、T
的方差,在某些情况下,U
。在 T
和 U
的情况下,那里的差异在于它们可能具有的任何生命周期参数,而不是类型本身。 IE,它描述了 'b
如何在 Struct<'b>
中变体(或不变),而不是如何将 Struct<'b>
转换为 dyn Trait + 'b
。
Treating a &Player
as &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
的结果 - 这不是解决问题的最佳方法。