Rust 动态分发

2025-05-20
use tracing::info;

use crate::daemon::monitors::PositionMonitor;
use crate::daemon::position_monitor::{
    monitor_position_retracement::RetracementMonitor, monitor_prosition_stop::TrendStopMonitor,
};
use crate::modules::position::get_position_info;

pub fn start_all_position_monitors(key_group: u32) {
    let result = get_position_info(None, None, Some(key_group));

    match result {
        Ok(position) => {
            if position.position_amt == "0.000" || position.side.is_empty() {
                info!("key_group {} 当前为空仓,跳过监控启动", key_group);
                return;
            }
            info!("📡 启动价格trait回撤监控线程(组号 {})", key_group);

            let entry_price = position.entry_price.parse().unwrap_or(0.0);

            let monitors: Vec<Box<dyn PositionMonitor>> = vec![
                Box::new(TrendStopMonitor {
                    key_group,
                    side: position.side.clone(),
                }),
                Box::new(RetracementMonitor {
                    key_group,
                    side: position.side.clone(),
                    entry_price,
                }),
            ];

            for monitor in monitors {
                std::thread::spawn(move || {
                    if let Err(e) = monitor.run_monitor_loop() {
                        info!("监控错误: {:?}", e);
                    }
                });
            }
        }
        Err(e) => {
            info!("获取持仓失败,key_group {}: {:?}", key_group, e);
        }
    }
}

这段代码创建了一个包含多个实现了 PositionMonitor trait 的监控器的动态数组(Vec<Box<dyn PositionMonitor>>),并通过 Box::new 将它们封装成堆分配的 trait 对象。以下是逐步解析:


1. 定义 monitors 动态数组

let monitors: Vec<Box<dyn PositionMonitor>> = vec![
    Box::new(TrendStopMonitor { /* ... */ }),
    Box::new(RetracementMonitor { /* ... */ }),
];
  • Vec<Box<dyn PositionMonitor>>
    这是一个动态数组,存储的是堆分配的 trait 对象Box<dyn PositionMonitor>),表示数组中可以存放任何实现了 PositionMonitor trait 的类型。
  • Box::new
    将具体类型的实例(如 TrendStopMonitor)分配到堆上,并转换为 Box<dyn PositionMonitor> 类型,以实现动态分发。

2. 初始化监控器实例

(1) TrendStopMonitor

Box::new(TrendStopMonitor {
    key_group,                  // 传入 key_group(u32)
    side: position.side.clone(), // 克隆持仓方向(String 或 &str)
})
  • 用于监控趋势止损逻辑。
  • 只需 key_groupside 两个字段。

(2) RetracementMonitor

Box::new(RetracementMonitor {
    key_group,                  // 传入 key_group
    side: position.side.clone(), // 克隆持仓方向
    entry_price,                // 传入 entry_price(f64)
})
  • 用于监控价格回撤逻辑。
  • TrendStopMonitor 多一个 entry_price 字段。

3. 为什么用 Box<dyn Trait>

  • 动态分发(Dynamic Dispatch)
    由于 Vec 要求所有元素类型相同,但 TrendStopMonitorRetracementMonitor 是不同类型,通过 Box<dyn PositionMonitor> 将它们统一视为 trait 对象,实现运行时多态。
  • 所有权管理
    Box 确保监控器实例的生命周期由堆内存管理,避免悬垂指针。

4. PositionMonitor trait 的作用

假设 PositionMonitor 定义如下(需在你的代码中存在):

pub trait PositionMonitor {
    fn run_monitor_loop(&self) -> Result<(), BinanceError>;
}
  • 所有监控器必须实现 run_monitor_loop 方法。
  • 后续的 for monitor in monitors 可以统一调用该方法,无需关心具体类型。

5. 代码执行流程

  1. 创建监控器列表
    • TrendStopMonitorRetracementMonitor 实例放入 Vec
  2. 启动线程监控
    • 遍历 monitors,为每个监控器启动一个独立线程:
      std::thread::spawn(move || {
          if let Err(e) = monitor.run_monitor_loop() {
              info!("监控错误: {:?}", e);
          }
      });
      
    • 每个线程会持续运行 run_monitor_loop(),直到出错或退出。

关键点总结

部分 说明
Vec<Box<dyn PositionMonitor>> 存储多种监控器的动态数组
Box::new(TrendStopMonitor { ... }) 将具体类型封装为 trait 对象
position.side.clone() 克隆 side 避免所有权问题
std::thread::spawn 为每个监控器启动独立线程

类比理解

想象成一个多设备控制中心

  • PositionMonitor 是“设备接口”(如支持 start() 方法)。
  • TrendStopMonitorRetracementMonitor 是两种不同的设备(如“温度传感器”和“湿度传感器”)。
  • Vec<Box<dyn PositionMonitor>> 是设备列表,可以统一管理它们。

改进建议

  1. 避免克隆 side
    如果 side&str 或能借用,改用 Cow<'_, str>&str 减少拷贝。
  2. 错误处理
    entry_price.parse() 直接 unwrap_or(0.0) 可能隐藏错误,建议用 ? 提前返回。
  3. 线程管理
    保存 JoinHandle 以便后续等待线程结束:
    let handles: Vec<_> = monitors.into_iter().map(|monitor| {
        std::thread::spawn(move || {
            monitor.run_monitor_loop().unwrap_or_else(|e| info!("监控错误: {:?}", e));
        })
    }).collect();
    

这样设计既灵活(支持多种监控器)又类型安全(通过 trait 约束)。

This code creates a dynamic array (Vec<Box<dyn PositionMonitor>>) of monitors that implement the PositionMonitor trait, and encapsulates them into heap-allocated trait objects via Box::new. Here is a step-by-step breakdown:


1. Define the monitors dynamic array

let monitors: Vec<Box<dyn PositionMonitor>> = vec![
Box::new(TrendStopMonitor { /* ... */ }),
Box::new(RetracementMonitor { /* ... */ }),
];
  • Vec<Box<dyn PositionMonitor>> This is a dynamic array that stores heap-allocated trait objects (Box<dyn PositionMonitor>), which means that any type that implements the PositionMonitor trait can be stored in the array.
  • Box::new Allocate instances of specific types (such as TrendStopMonitor) to the heap and convert them to Box<dyn PositionMonitor> type for dynamic distribution.

2. Initialize monitor instance

(1) TrendStopMonitor

Box::new(TrendStopMonitor {
key_group, // pass in key_group (u32)
side: position.side.clone(), // clone position direction (String or &str)
})
  • Used to monitor trend stop loss logic.
  • Only two fields are needed: key_group and side.

(2) RetracementMonitor

Box::new(RetracementMonitor {
key_group, // pass in key_group
side: position.side.clone(), // clone position direction
entry_price, // pass in entry_price(f64)
})
  • Used to monitor price retracement logic.
  • One more entry_price field than TrendStopMonitor.

**3. Why use Box<dyn Trait>? **

  • Dynamic Dispatch Since Vec requires all elements to be of the same type, but TrendStopMonitor and RetracementMonitor are of different types, Box<dyn PositionMonitor> is used to treat them as trait objects to achieve runtime polymorphism.
  • Ownership management Box ensures that the lifecycle of the monitor instance is managed by heap memory to avoid dangling pointers.

4. The role of the PositionMonitor trait

Assume that PositionMonitor is defined as follows (it must exist in your code):

pub trait PositionMonitor {
fn run_monitor_loop(&self) -> Result<(), BinanceError>;
}
  • All monitors must implement the run_monitor_loop method.
  • Subsequent for monitor in monitors can call this method uniformly without caring about the specific type.

5. Code execution flow

  1. Create a monitor list
    • Put TrendStopMonitor and RetracementMonitor instances into Vec.
  2. Start thread monitoring
    • Traverse monitors and start a separate thread for each monitor:
      std::thread::spawn(move || {
      if let Err(e) = monitor.run_monitor_loop() {
      info!("Monitor error: {:?}", e);
      }
      });
      
    • Each thread will continue to run run_monitor_loop() until an error or exit.

Key Points

Section Description
Vec<Box<dyn PositionMonitor>> Dynamic array for storing multiple monitors
Box::new(TrendStopMonitor { ... }) Encapsulate specific types as trait objects
position.side.clone() Clone side to avoid ownership issues
std::thread::spawn Start a separate thread for each monitor

Analogy

Imagine a multi-device control center:

  • PositionMonitor is a “device interface” (e.g. supports start() method).
  • TrendStopMonitor and RetracementMonitor are two different devices (e.g. “temperature sensor” and “humidity sensor”).
  • Vec<Box<dyn PositionMonitor>> is a list of devices that can be managed in a unified way.

Improvement suggestions

  1. Avoid cloning side If side is &str or can be borrowed, use Cow<'_, str> or &str to reduce copying.
  2. Error handling entry_price.parse() directly unwrap_or(0.0) may hide errors, it is recommended to use ? to return in advance.
  3. Thread management Save JoinHandle to wait for the thread to end later:
    let handles: Vec<_> = monitors.into_iter().map(|monitor| {
    std::thread::spawn(move || {
    monitor.run_monitor_loop().unwrap_or_else(|e| info!("Monitor error: {:?}", e));
    })
    }).collect();
    

This design is both flexible (supporting multiple monitors) and type-safe (through trait constraints).