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>),表示数组中可以存放任何实现了PositionMonitortrait 的类型。Box::new
将具体类型的实例(如TrendStopMonitor)分配到堆上,并转换为Box<dyn PositionMonitor>类型,以实现动态分发。
2. 初始化监控器实例
(1) TrendStopMonitor
Box::new(TrendStopMonitor {
key_group, // 传入 key_group(u32)
side: position.side.clone(), // 克隆持仓方向(String 或 &str)
})
- 用于监控趋势止损逻辑。
- 只需
key_group和side两个字段。
(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要求所有元素类型相同,但TrendStopMonitor和RetracementMonitor是不同类型,通过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. 代码执行流程
- 创建监控器列表
- 将
TrendStopMonitor和RetracementMonitor实例放入Vec。
- 将
- 启动线程监控
- 遍历
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()方法)。TrendStopMonitor和RetracementMonitor是两种不同的设备(如“温度传感器”和“湿度传感器”)。Vec<Box<dyn PositionMonitor>>是设备列表,可以统一管理它们。
改进建议
- 避免克隆
side
如果side是&str或能借用,改用Cow<'_, str>或&str减少拷贝。 - 错误处理
entry_price.parse()直接unwrap_or(0.0)可能隐藏错误,建议用?提前返回。 - 线程管理
保存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 thePositionMonitortrait can be stored in the array.Box::newAllocate instances of specific types (such asTrendStopMonitor) to the heap and convert them toBox<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_groupandside.
(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_pricefield thanTrendStopMonitor.
**3. Why use Box<dyn Trait>? **
- Dynamic Dispatch
Since
Vecrequires all elements to be of the same type, butTrendStopMonitorandRetracementMonitorare of different types,Box<dyn PositionMonitor>is used to treat them as trait objects to achieve runtime polymorphism. - Ownership management
Boxensures 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_loopmethod. - Subsequent
for monitor in monitorscan call this method uniformly without caring about the specific type.
5. Code execution flow
- Create a monitor list
- Put
TrendStopMonitorandRetracementMonitorinstances intoVec.
- Put
- Start thread monitoring
- Traverse
monitorsand 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.
- Traverse
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:
PositionMonitoris a “device interface” (e.g. supportsstart()method).TrendStopMonitorandRetracementMonitorare 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
- Avoid cloning
sideIfsideis&stror can be borrowed, useCow<'_, str>or&strto reduce copying. - Error handling
entry_price.parse()directlyunwrap_or(0.0)may hide errors, it is recommended to use?to return in advance. - Thread management
Save
JoinHandleto 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).