响应式编程是一种编程范式,专注于数据流和变化传播。在 Exercism 的 "react" 练习中,我们需要实现一个简单的响应式系统,类似于 ReactJS 或其他前端框架中的状态管理系统。这不仅能帮助我们掌握响应式编程的核心概念,还能深入学习Rust中的所有权、生命周期、闭包和复杂数据结构设计。
什么是响应式编程?
响应式编程是一种声明式的编程范式,关注于数据流和变化传播。在响应式系统中,当数据发生变化时,相关的计算会自动重新执行,依赖这些计算的组件也会自动更新。
在这个练习中,我们需要实现一个简单的响应式系统,包含以下组件:
- 输入单元(Input Cells):包含可变值的基本单元
- 计算单元(Compute Cells):基于其他单元的值进行计算的单元
- 回调(Callbacks):当计算单元的值发生变化时触发的函数
让我们先看看练习提供的结构体和函数:
rust
/// `InputCellId` is a unique identifier for an input cell.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct InputCellId();
/// `ComputeCellId` is a unique identifier for a compute cell.
/// Values of type `InputCellId` and `ComputeCellId` should not be mutually assignable,
/// demonstrated by the following tests:
///
/// ```compile_fail
/// let mut r = react::Reactor::new();
/// let input: react::ComputeCellId = r.create_input(111);
/// ```
///
/// ```compile_fail
/// let mut r = react::Reactor::new();
/// let input = r.create_input(111);
/// let compute: react::InputCellId = r.create_compute(&[react::CellId::Input(input)], |_| 222).unwrap();
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ComputeCellId();
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CallbackId();
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
pub struct Reactor<T> {
// Just so that the compiler doesn't complain about an unused type parameter.
// You probably want to delete this field.
dummy: ::std::marker::PhantomData<T>,
}
// You are guaranteed that Reactor will only be tested against types that are Copy + PartialEq.
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
unimplemented!()
}
// Creates an input cell with the specified initial value, returning its ID.
pub fn create_input(&mut self, _initial: T) -> InputCellId {
unimplemented!()
}
// Creates a compute cell with the specified dependencies and compute function.
// The compute function is expected to take in its arguments in the same order as specified in
// `dependencies`.
// You do not need to reject compute functions that expect more arguments than there are
// dependencies (how would you check for this, anyway?).
//
// If any dependency doesn't exist, returns an Err with that nonexistent dependency.
// (If multiple dependencies do not exist, exactly which one is returned is not defined and
// will not be tested)
//
// Notice that there is no way to *remove* a cell.
// This means that you may assume, without checking, that if the dependencies exist at creation
// time they will continue to exist as long as the Reactor exists.
pub fn create_compute<F: Fn(&[T]) -> T>(
&mut self,
_dependencies: &[CellId],
_compute_func: F,
) -> Result<ComputeCellId, CellId> {
unimplemented!()
}
// Retrieves the current value of the cell, or None if the cell does not exist.
//
// You may wonder whether it is possible to implement `get(&self, id: CellId) -> Option<&Cell>`
// and have a `value(&self)` method on `Cell`.
//
// It turns out this introduces a significant amount of extra complexity to this exercise.
// We chose not to cover this here, since this exercise is probably enough work as-is.
pub fn value(&self, id: CellId) -> Option<T> {
unimplemented!("Get the value of the cell whose id is {:?}", id)
}
// Sets the value of the specified input cell.
//
// Returns false if the cell does not exist.
//
// Similarly, you may wonder about `get_mut(&mut self, id: CellId) -> Option<&mut Cell>`, with
// a `set_value(&mut self, new_value: T)` method on `Cell`.
//
// As before, that turned out to add too much extra complexity.
pub fn set_value(&mut self, _id: InputCellId, _new_value: T) -> bool {
unimplemented!()
}
// Adds a callback to the specified compute cell.
//
// Returns the ID of the just-added callback, or None if the cell doesn't exist.
//
// Callbacks on input cells will not be tested.
//
// The semantics of callbacks (as will be tested):
// For a single set_value call, each compute cell's callbacks should each be called:
// * Zero times if the compute cell's value did not change as a result of the set_value call.
// * Exactly once if the compute cell's value changed as a result of the set_value call.
// The value passed to the callback should be the final value of the compute cell after the
// set_value call.
pub fn add_callback<F: FnMut(T)>(
&mut self,
_id: ComputeCellId,
_callback: F,
) -> Option<CallbackId> {
unimplemented!()
}
// Removes the specified callback, using an ID returned from add_callback.
//
// Returns an Err if either the cell or callback does not exist.
//
// A removed callback should no longer be called.
pub fn remove_callback(
&mut self,
cell: ComputeCellId,
callback: CallbackId,
) -> Result<(), RemoveCallbackError> {
unimplemented!(
"Remove the callback identified by the CallbackId {:?} from the cell {:?}",
callback,
cell,
)
}
}
我们需要实现 Reactor 结构体,使其能够管理输入单元、计算单元和回调函数,形成一个简单的响应式系统。
设计分析
1. 核心要求
- 单元管理:管理输入单元和计算单元的创建和存储
- 依赖跟踪:跟踪计算单元对其依赖单元的依赖关系
- 值传播:当输入单元值改变时,自动更新相关计算单元
- 回调机制:当计算单元值发生变化时触发回调函数
- 类型安全:确保 InputCellId 和 ComputeCellId 不能互换使用
2. 技术要点
- 所有权和生命周期:正确管理单元和回调函数的所有权
- 闭包处理:存储和调用可变闭包
- 数据结构设计:设计合适的数据结构存储单元和依赖关系
- 变化检测:检测值的变化以决定是否触发回调
- 错误处理:处理不存在的单元和回调
完整实现
1. 基础实现
rust
use std::collections::HashMap;
/// `InputCellId` is a unique identifier for an input cell.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct InputCellId(usize);
/// `ComputeCellId` is a unique identifier for a compute cell.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ComputeCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CallbackId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
// 输入单元
struct InputCell<T> {
value: T,
}
// 计算单元
struct ComputeCell<T> {
dependencies: Vec<CellId>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
callbacks: HashMap<CallbackId, Box<dyn FnMut(T)>>,
}
pub struct Reactor<T: Copy + PartialEq> {
input_cells: HashMap<InputCellId, InputCell<T>>,
compute_cells: HashMap<ComputeCellId, ComputeCell<T>>,
next_input_id: usize,
next_compute_id: usize,
next_callback_id: usize,
}
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
Reactor {
input_cells: HashMap::new(),
compute_cells: HashMap::new(),
next_input_id: 0,
next_compute_id: 0,
next_callback_id: 0,
}
}
// Creates an input cell with the specified initial value, returning its ID.
pub fn create_input(&mut self, initial: T) -> InputCellId {
let id = InputCellId(self.next_input_id);
self.next_input_id += 1;
self.input_cells.insert(id, InputCell { value: initial });
id
}
// Creates a compute cell with the specified dependencies and compute function.
pub fn create_compute<F: Fn(&[T]) -> T + 'static>(
&mut self,
dependencies: &[CellId],
compute_func: F,
) -> Result<ComputeCellId, CellId> {
// 检查所有依赖是否存在
for &dep in dependencies {
match dep {
CellId::Input(id) => {
if !self.input_cells.contains_key(&id) {
return Err(CellId::Input(id));
}
}
CellId::Compute(id) => {
if !self.compute_cells.contains_key(&id) {
return Err(CellId::Compute(id));
}
}
}
}
// 计算初始值
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let initial_value = compute_func(&values);
let id = ComputeCellId(self.next_compute_id);
self.next_compute_id += 1;
let compute_cell = ComputeCell {
dependencies: dependencies.to_vec(),
compute_func: Box::new(compute_func),
value: initial_value,
callbacks: HashMap::new(),
};
self.compute_cells.insert(id, compute_cell);
Ok(id)
}
// Retrieves the current value of the cell, or None if the cell does not exist.
pub fn value(&self, id: CellId) -> Option<T> {
match id {
CellId::Input(id) => self.input_cells.get(&id).map(|cell| cell.value),
CellId::Compute(id) => self.compute_cells.get(&id).map(|cell| cell.value),
}
}
// Sets the value of the specified input cell.
pub fn set_value(&mut self, id: InputCellId, new_value: T) -> bool {
let cell = match self.input_cells.get_mut(&id) {
Some(cell) => cell,
None => return false,
};
if cell.value == new_value {
return true;
}
cell.value = new_value;
self.update_dependents(CellId::Input(id));
true
}
// 添加回调函数
pub fn add_callback<F: FnMut(T) + 'static>(
&mut self,
id: ComputeCellId,
callback: F,
) -> Option<CallbackId> {
let cell = self.compute_cells.get_mut(&id)?;
let callback_id = CallbackId(self.next_callback_id);
self.next_callback_id += 1;
cell.callbacks.insert(callback_id, Box::new(callback));
Some(callback_id)
}
// 移除回调函数
pub fn remove_callback(
&mut self,
cell_id: ComputeCellId,
callback_id: CallbackId,
) -> Result<(), RemoveCallbackError> {
let cell = self
.compute_cells
.get_mut(&cell_id)
.ok_or(RemoveCallbackError::NonexistentCell)?;
if cell.callbacks.remove(&callback_id).is_some() {
Ok(())
} else {
Err(RemoveCallbackError::NonexistentCallback)
}
}
// 更新依赖单元
fn update_dependents(&mut self, changed_cell: CellId) {
let mut affected_cells = Vec::new();
// 找到所有直接依赖于 changed_cell 的计算单元
for (&id, cell) in &self.compute_cells {
if cell.dependencies.contains(&changed_cell) {
affected_cells.push(id);
}
}
// 递归更新所有受影响的计算单元
self.update_compute_cells(affected_cells);
}
// 更新计算单元
fn update_compute_cells(&mut self, cells_to_update: Vec<ComputeCellId>) {
for id in cells_to_update {
if let Some(cell) = self.compute_cells.get(&id) {
let dependencies = cell.dependencies.clone();
let compute_func = &cell.compute_func;
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let new_value = compute_func(&values);
if let Some(cell) = self.compute_cells.get_mut(&id) {
let old_value = cell.value;
cell.value = new_value;
// 如果值发生变化,调用回调函数
if old_value != new_value {
let callbacks: Vec<(CallbackId, Box<dyn FnMut(T)>)> =
cell.callbacks.drain().collect();
for (callback_id, mut callback) in callbacks {
callback(new_value);
cell.callbacks.insert(callback_id, callback);
}
}
// 继续更新依赖于此单元的其他单元
self.update_dependents(CellId::Compute(id));
}
}
}
}
}
2. 优化实现
rust
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InputCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ComputeCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CallbackId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
struct InputCell<T> {
value: T,
}
struct ComputeCell<T> {
dependencies: Vec<CellId>,
reverse_dependencies: Vec<ComputeCellId>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
callbacks: HashMap<CallbackId, Box<dyn FnMut(T)>>,
}
pub struct Reactor<T: Copy + PartialEq> {
input_cells: HashMap<InputCellId, InputCell<T>>,
compute_cells: HashMap<ComputeCellId, ComputeCell<T>>,
next_input_id: usize,
next_compute_id: usize,
next_callback_id: usize,
}
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
Reactor {
input_cells: HashMap::new(),
compute_cells: HashMap::new(),
next_input_id: 0,
next_compute_id: 0,
next_callback_id: 0,
}
}
pub fn create_input(&mut self, initial: T) -> InputCellId {
let id = InputCellId(self.next_input_id);
self.next_input_id += 1;
self.input_cells.insert(id, InputCell { value: initial });
id
}
pub fn create_compute<F: Fn(&[T]) -> T + 'static>(
&mut self,
dependencies: &[CellId],
compute_func: F,
) -> Result<ComputeCellId, CellId> {
// 检查所有依赖是否存在
for &dep in dependencies {
match dep {
CellId::Input(id) => {
if !self.input_cells.contains_key(&id) {
return Err(CellId::Input(id));
}
}
CellId::Compute(id) => {
if !self.compute_cells.contains_key(&id) {
return Err(CellId::Compute(id));
}
}
}
}
// 计算初始值
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let initial_value = compute_func(&values);
let id = ComputeCellId(self.next_compute_id);
self.next_compute_id += 1;
// 更新反向依赖关系
for &dep in dependencies {
if let CellId::Compute(dep_id) = dep {
if let Some(dep_cell) = self.compute_cells.get_mut(&dep_id) {
dep_cell.reverse_dependencies.push(id);
}
}
}
let compute_cell = ComputeCell {
dependencies: dependencies.to_vec(),
reverse_dependencies: Vec::new(),
compute_func: Box::new(compute_func),
value: initial_value,
callbacks: HashMap::new(),
};
self.compute_cells.insert(id, compute_cell);
Ok(id)
}
pub fn value(&self, id: CellId) -> Option<T> {
match id {
CellId::Input(id) => self.input_cells.get(&id).map(|cell| cell.value),
CellId::Compute(id) => self.compute_cells.get(&id).map(|cell| cell.value),
}
}
pub fn set_value(&mut self, id: InputCellId, new_value: T) -> bool {
let cell = match self.input_cells.get_mut(&id) {
Some(cell) => cell,
None => return false,
};
if cell.value == new_value {
return true;
}
cell.value = new_value;
self.update_dependents(CellId::Input(id));
true
}
pub fn add_callback<F: FnMut(T) + 'static>(
&mut self,
id: ComputeCellId,
callback: F,
) -> Option<CallbackId> {
let cell = self.compute_cells.get_mut(&id)?;
let callback_id = CallbackId(self.next_callback_id);
self.next_callback_id += 1;
cell.callbacks.insert(callback_id, Box::new(callback));
Some(callback_id)
}
pub fn remove_callback(
&mut self,
cell_id: ComputeCellId,
callback_id: CallbackId,
) -> Result<(), RemoveCallbackError> {
let cell = self
.compute_cells
.get_mut(&cell_id)
.ok_or(RemoveCallbackError::NonexistentCell)?;
if cell.callbacks.remove(&callback_id).is_some() {
Ok(())
} else {
Err(RemoveCallbackError::NonexistentCallback)
}
}
fn update_dependents(&mut self, changed_cell: CellId) {
let dependents = match changed_cell {
CellId::Input(_) => {
// 收集直接依赖于该输入单元的所有计算单元
self.compute_cells
.iter()
.filter_map(|(&id, cell)| {
if cell.dependencies.contains(&changed_cell) {
Some(id)
} else {
None
}
})
.collect::<Vec<_>>()
}
CellId::Compute(id) => {
// 使用反向依赖关系
if let Some(cell) = self.compute_cells.get(&id) {
cell.reverse_dependencies.clone()
} else {
Vec::new()
}
}
};
self.update_compute_cells(dependents);
}
fn update_compute_cells(&mut self, cells_to_update: Vec<ComputeCellId>) {
for id in cells_to_update {
if let Some(cell) = self.compute_cells.get(&id) {
let dependencies = cell.dependencies.clone();
let compute_func = &cell.compute_func;
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let new_value = compute_func(&values);
if let Some(cell) = self.compute_cells.get_mut(&id) {
let old_value = cell.value;
cell.value = new_value;
// 如果值发生变化,调用回调函数
if old_value != new_value {
let callbacks: Vec<(CallbackId, Box<dyn FnMut(T)>)> =
cell.callbacks.iter_mut()
.map(|(&id, callback)| (id, std::mem::replace(callback, Box::new(|_| {}))))
.collect();
for (callback_id, mut callback) in callbacks {
callback(new_value);
cell.callbacks.insert(callback_id, callback);
}
}
// 继续更新依赖于此单元的其他单元
if old_value != new_value {
self.update_dependents(CellId::Compute(id));
}
}
}
}
}
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
rust
#[test]
fn input_cells_have_a_value() {
let mut reactor = Reactor::new();
let input = reactor.create_input(10);
assert_eq!(reactor.value(CellId::Input(input)), Some(10));
}
输入单元应该有初始值。
rust
#[test]
fn an_input_cells_value_can_be_set() {
let mut reactor = Reactor::new();
let input = reactor.create_input(4);
assert!(reactor.set_value(input, 20));
assert_eq!(reactor.value(CellId::Input(input)), Some(20));
}
输入单元的值可以被设置。
rust
#[test]
fn error_setting_a_nonexistent_input_cell() {
let mut dummy_reactor = Reactor::new();
let input = dummy_reactor.create_input(1);
assert!(!Reactor::new().set_value(input, 0));
}
设置不存在的输入单元应该返回false。
rust
#[test]
fn compute_cells_calculate_initial_value() {
let mut reactor = Reactor::new();
let input = reactor.create_input(1);
let output = reactor
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
.unwrap();
assert_eq!(reactor.value(CellId::Compute(output)), Some(2));
}
计算单元应该计算初始值。
rust
#[test]
fn compute_cells_take_inputs_in_the_right_order() {
let mut reactor = Reactor::new();
let one = reactor.create_input(1);
let two = reactor.create_input(2);
let output = reactor
.create_compute(&[CellId::Input(one), CellId::Input(two)], |v| {
v[0] + v[1] * 10
})
.unwrap();
assert_eq!(reactor.value(CellId::Compute(output)), Some(21));
}
计算单元应该按照依赖顺序接收输入。
rust
#[test]
fn error_creating_compute_cell_if_input_doesnt_exist() {
let mut dummy_reactor = Reactor::new();
let input = dummy_reactor.create_input(1);
assert_eq!(
Reactor::new().create_compute(&[CellId::Input(input)], |_| 0),
Err(CellId::Input(input))
);
}
如果依赖不存在,创建计算单元应该返回错误。
rust
#[test]
fn do_not_break_cell_if_creating_compute_cell_with_valid_and_invalid_input() {
let mut dummy_reactor = Reactor::new();
let _ = dummy_reactor.create_input(1);
let dummy_cell = dummy_reactor.create_input(2);
let mut reactor = Reactor::new();
let input = reactor.create_input(1);
assert_eq!(
reactor.create_compute(&[CellId::Input(input), CellId::Input(dummy_cell)], |_| 0),
Err(CellId::Input(dummy_cell))
);
assert!(reactor.set_value(input, 5));
assert_eq!(reactor.value(CellId::Input(input)), Some(5));
}
创建计算单元时,即使有有效和无效的输入,也不应该破坏现有单元。
rust
#[test]
fn compute_cells_update_value_when_dependencies_are_changed() {
let mut reactor = Reactor::new();
let input = reactor.create_input(1);
let output = reactor
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
.unwrap();
assert_eq!(reactor.value(CellId::Compute(output)), Some(2));
assert!(reactor.set_value(input, 3));
assert_eq!(reactor.value(CellId::Compute(output)), Some(4));
}
当依赖改变时,计算单元应该更新值。
rust
#[test]
fn compute_cells_can_depend_on_other_compute_cells() {
let mut reactor = Reactor::new();
let input = reactor.create_input(1);
let times_two = reactor
.create_compute(&[CellId::Input(input)], |v| v[0] * 2)
.unwrap();
let times_thirty = reactor
.create_compute(&[CellId::Input(input)], |v| v[0] * 30)
.unwrap();
let output = reactor
.create_compute(
&[CellId::Compute(times_two), CellId::Compute(times_thirty)],
|v| v[0] + v[1],
)
.unwrap();
assert_eq!(reactor.value(CellId::Compute(output)), Some(32));
assert!(reactor.set_value(input, 3));
assert_eq!(reactor.value(CellId::Compute(output)), Some(96));
}
计算单元可以依赖于其他计算单元。
性能优化版本
考虑性能的优化实现:
rust
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InputCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ComputeCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CallbackId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
struct InputCell<T> {
value: T,
}
struct ComputeCell<T> {
dependencies: Vec<CellId>,
reverse_dependencies: Vec<ComputeCellId>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
callbacks: HashMap<CallbackId, Box<dyn FnMut(T)>>,
}
pub struct Reactor<T: Copy + PartialEq> {
input_cells: HashMap<InputCellId, InputCell<T>>,
compute_cells: HashMap<ComputeCellId, ComputeCell<T>>,
next_input_id: usize,
next_compute_id: usize,
next_callback_id: usize,
}
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
Reactor {
input_cells: HashMap::new(),
compute_cells: HashMap::new(),
next_input_id: 0,
next_compute_id: 0,
next_callback_id: 0,
}
}
pub fn create_input(&mut self, initial: T) -> InputCellId {
let id = InputCellId(self.next_input_id);
self.next_input_id += 1;
self.input_cells.insert(id, InputCell { value: initial });
id
}
pub fn create_compute<F: Fn(&[T]) -> T + 'static>(
&mut self,
dependencies: &[CellId],
compute_func: F,
) -> Result<ComputeCellId, CellId> {
// 检查所有依赖是否存在
for &dep in dependencies {
match dep {
CellId::Input(id) => {
if !self.input_cells.contains_key(&id) {
return Err(CellId::Input(id));
}
}
CellId::Compute(id) => {
if !self.compute_cells.contains_key(&id) {
return Err(CellId::Compute(id));
}
}
}
}
// 计算初始值
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let initial_value = compute_func(&values);
let id = ComputeCellId(self.next_compute_id);
self.next_compute_id += 1;
// 更新反向依赖关系
for &dep in dependencies {
if let CellId::Compute(dep_id) = dep {
if let Some(dep_cell) = self.compute_cells.get_mut(&dep_id) {
dep_cell.reverse_dependencies.push(id);
}
}
}
let compute_cell = ComputeCell {
dependencies: dependencies.to_vec(),
reverse_dependencies: Vec::new(),
compute_func: Box::new(compute_func),
value: initial_value,
callbacks: HashMap::new(),
};
self.compute_cells.insert(id, compute_cell);
Ok(id)
}
pub fn value(&self, id: CellId) -> Option<T> {
match id {
CellId::Input(id) => self.input_cells.get(&id).map(|cell| cell.value),
CellId::Compute(id) => self.compute_cells.get(&id).map(|cell| cell.value),
}
}
pub fn set_value(&mut self, id: InputCellId, new_value: T) -> bool {
let cell = match self.input_cells.get_mut(&id) {
Some(cell) => cell,
None => return false,
};
if cell.value == new_value {
return true;
}
cell.value = new_value;
self.update_dependents(CellId::Input(id));
true
}
pub fn add_callback<F: FnMut(T) + 'static>(
&mut self,
id: ComputeCellId,
callback: F,
) -> Option<CallbackId> {
let cell = self.compute_cells.get_mut(&id)?;
let callback_id = CallbackId(self.next_callback_id);
self.next_callback_id += 1;
cell.callbacks.insert(callback_id, Box::new(callback));
Some(callback_id)
}
pub fn remove_callback(
&mut self,
cell_id: ComputeCellId,
callback_id: CallbackId,
) -> Result<(), RemoveCallbackError> {
let cell = self
.compute_cells
.get_mut(&cell_id)
.ok_or(RemoveCallbackError::NonexistentCell)?;
if cell.callbacks.remove(&callback_id).is_some() {
Ok(())
} else {
Err(RemoveCallbackError::NonexistentCallback)
}
}
fn update_dependents(&mut self, changed_cell: CellId) {
let mut affected_cells = Vec::new();
match changed_cell {
CellId::Input(_) => {
// 收集直接依赖于该输入单元的所有计算单元
for (&id, cell) in &self.compute_cells {
if cell.dependencies.contains(&changed_cell) {
affected_cells.push(id);
}
}
}
CellId::Compute(id) => {
// 使用反向依赖关系
if let Some(cell) = self.compute_cells.get(&id) {
affected_cells.extend(&cell.reverse_dependencies);
}
}
}
self.update_compute_cells(affected_cells);
}
fn update_compute_cells(&mut self, mut cells_to_update: Vec<ComputeCellId>) {
let mut visited = std::collections::HashSet::new();
while let Some(id) = cells_to_update.pop() {
// 避免重复处理
if !visited.insert(id) {
continue;
}
if let Some(cell) = self.compute_cells.get(&id) {
let dependencies = cell.dependencies.clone();
let compute_func = &cell.compute_func;
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let new_value = compute_func(&values);
if let Some(cell) = self.compute_cells.get_mut(&id) {
let old_value = cell.value;
cell.value = new_value;
// 如果值发生变化,调用回调函数
if old_value != new_value {
// 创建回调函数的副本以避免借用冲突
let callbacks: Vec<(CallbackId, Box<dyn FnMut(T)>)> =
cell.callbacks.iter_mut()
.map(|(&id, callback)| {
(id, std::mem::replace(callback, Box::new(|_| {})))
})
.collect();
// 调用回调函数
for (callback_id, mut callback) in callbacks {
callback(new_value);
cell.callbacks.insert(callback_id, callback);
}
// 将依赖于此单元的单元添加到更新队列
cells_to_update.extend(&cell.reverse_dependencies);
}
}
}
}
}
}
// 使用引用计数的版本
use std::rc::{Rc, Weak};
use std::cell::RefCell;
pub struct RcReactor<T: Copy + PartialEq> {
cells: Vec<Rc<RefCell<dyn Cell<T>>>>,
}
trait Cell<T> {
fn value(&self) -> T;
fn add_dependency(&mut self, dep: Weak<RefCell<dyn Cell<T>>>);
}
struct RcInputCell<T> {
value: T,
}
struct RcComputeCell<T> {
dependencies: Vec<Weak<RefCell<dyn Cell<T>>>>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
}
impl<T: Copy + PartialEq> RcReactor<T> {
pub fn new() -> Self {
RcReactor {
cells: Vec::new(),
}
}
pub fn create_input(&mut self, initial: T) -> usize {
let cell = Rc::new(RefCell::new(RcInputCell { value: initial }));
self.cells.push(cell);
self.cells.len() - 1
}
}
错误处理和边界情况
考虑更多边界情况的实现:
rust
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InputCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ComputeCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CallbackId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
#[derive(Debug, PartialEq)]
pub enum ReactorError<T> {
NonexistentCell,
DependencyNotFound(CellId),
CallbackError(RemoveCallbackError),
ValueError(T),
}
impl<T> From<RemoveCallbackError> for ReactorError<T> {
fn from(err: RemoveCallbackError) -> Self {
ReactorError::CallbackError(err)
}
}
struct InputCell<T> {
value: T,
}
struct ComputeCell<T> {
dependencies: Vec<CellId>,
reverse_dependencies: Vec<ComputeCellId>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
callbacks: HashMap<CallbackId, Box<dyn FnMut(T)>>,
}
pub struct Reactor<T: Copy + PartialEq> {
input_cells: HashMap<InputCellId, InputCell<T>>,
compute_cells: HashMap<ComputeCellId, ComputeCell<T>>,
next_input_id: usize,
next_compute_id: usize,
next_callback_id: usize,
}
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
Reactor {
input_cells: HashMap::new(),
compute_cells: HashMap::new(),
next_input_id: 0,
next_compute_id: 0,
next_callback_id: 0,
}
}
pub fn create_input(&mut self, initial: T) -> InputCellId {
let id = InputCellId(self.next_input_id);
self.next_input_id += 1;
self.input_cells.insert(id, InputCell { value: initial });
id
}
pub fn create_compute<F: Fn(&[T]) -> T + 'static>(
&mut self,
dependencies: &[CellId],
compute_func: F,
) -> Result<ComputeCellId, CellId> {
// 检查所有依赖是否存在
for &dep in dependencies {
match dep {
CellId::Input(id) => {
if !self.input_cells.contains_key(&id) {
return Err(CellId::Input(id));
}
}
CellId::Compute(id) => {
if !self.compute_cells.contains_key(&id) {
return Err(CellId::Compute(id));
}
}
}
}
// 计算初始值
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let initial_value = compute_func(&values);
let id = ComputeCellId(self.next_compute_id);
self.next_compute_id += 1;
// 更新反向依赖关系
for &dep in dependencies {
if let CellId::Compute(dep_id) = dep {
if let Some(dep_cell) = self.compute_cells.get_mut(&dep_id) {
if !dep_cell.reverse_dependencies.contains(&id) {
dep_cell.reverse_dependencies.push(id);
}
}
}
}
let compute_cell = ComputeCell {
dependencies: dependencies.to_vec(),
reverse_dependencies: Vec::new(),
compute_func: Box::new(compute_func),
value: initial_value,
callbacks: HashMap::new(),
};
self.compute_cells.insert(id, compute_cell);
Ok(id)
}
pub fn value(&self, id: CellId) -> Option<T> {
match id {
CellId::Input(id) => self.input_cells.get(&id).map(|cell| cell.value),
CellId::Compute(id) => self.compute_cells.get(&id).map(|cell| cell.value),
}
}
pub fn set_value(&mut self, id: InputCellId, new_value: T) -> bool {
let cell = match self.input_cells.get_mut(&id) {
Some(cell) => cell,
None => return false,
};
if cell.value == new_value {
return true;
}
cell.value = new_value;
self.update_dependents(CellId::Input(id));
true
}
pub fn add_callback<F: FnMut(T) + 'static>(
&mut self,
id: ComputeCellId,
callback: F,
) -> Option<CallbackId> {
let cell = self.compute_cells.get_mut(&id)?;
let callback_id = CallbackId(self.next_callback_id);
self.next_callback_id += 1;
cell.callbacks.insert(callback_id, Box::new(callback));
Some(callback_id)
}
pub fn remove_callback(
&mut self,
cell_id: ComputeCellId,
callback_id: CallbackId,
) -> Result<(), RemoveCallbackError> {
let cell = self
.compute_cells
.get_mut(&cell_id)
.ok_or(RemoveCallbackError::NonexistentCell)?;
if cell.callbacks.remove(&callback_id).is_some() {
Ok(())
} else {
Err(RemoveCallbackError::NonexistentCallback)
}
}
// 返回Result的版本
pub fn set_value_safe(&mut self, id: InputCellId, new_value: T) -> Result<bool, ReactorError<T>> {
if !self.input_cells.contains_key(&id) {
return Err(ReactorError::NonexistentCell);
}
Ok(self.set_value(id, new_value))
}
pub fn value_safe(&self, id: CellId) -> Result<Option<T>, ReactorError<T>> {
match self.value(id) {
Some(value) => Ok(Some(value)),
None => Err(ReactorError::NonexistentCell),
}
}
fn update_dependents(&mut self, changed_cell: CellId) {
let mut affected_cells = Vec::new();
match changed_cell {
CellId::Input(_) => {
// 收集直接依赖于该输入单元的所有计算单元
for (&id, cell) in &self.compute_cells {
if cell.dependencies.contains(&changed_cell) {
affected_cells.push(id);
}
}
}
CellId::Compute(id) => {
// 使用反向依赖关系
if let Some(cell) = self.compute_cells.get(&id) {
affected_cells.extend(&cell.reverse_dependencies);
}
}
}
self.update_compute_cells(affected_cells);
}
fn update_compute_cells(&mut self, mut cells_to_update: Vec<ComputeCellId>) {
let mut visited = std::collections::HashSet::new();
while let Some(id) = cells_to_update.pop() {
// 避免重复处理
if !visited.insert(id) {
continue;
}
if let Some(cell) = self.compute_cells.get(&id) {
let dependencies = cell.dependencies.clone();
let compute_func = &cell.compute_func;
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let new_value = compute_func(&values);
if let Some(cell) = self.compute_cells.get_mut(&id) {
let old_value = cell.value;
cell.value = new_value;
// 如果值发生变化,调用回调函数
if old_value != new_value {
// 创建临时向量以避免借用冲突
let callback_ids: Vec<CallbackId> = cell.callbacks.keys().cloned().collect();
let mut callbacks_to_call = Vec::new();
for callback_id in callback_ids {
if let Some(callback) = cell.callbacks.get_mut(&callback_id) {
let callback = std::mem::replace(callback, Box::new(|_| {}));
callbacks_to_call.push((callback_id, callback, new_value));
}
}
// 调用回调函数
for (callback_id, mut callback, value) in callbacks_to_call {
callback(value);
cell.callbacks.insert(callback_id, callback);
}
// 将依赖于此单元的单元添加到更新队列
cells_to_update.extend(&cell.reverse_dependencies);
}
}
}
}
}
}
扩展功能
基于基础实现,我们可以添加更多功能:
rust
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InputCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ComputeCellId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CallbackId(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CellId {
Input(InputCellId),
Compute(ComputeCellId),
}
#[derive(Debug, PartialEq)]
pub enum RemoveCallbackError {
NonexistentCell,
NonexistentCallback,
}
struct InputCell<T> {
value: T,
}
struct ComputeCell<T> {
dependencies: Vec<CellId>,
reverse_dependencies: Vec<ComputeCellId>,
compute_func: Box<dyn Fn(&[T]) -> T>,
value: T,
callbacks: HashMap<CallbackId, Box<dyn FnMut(T)>>,
}
pub struct Reactor<T: Copy + PartialEq> {
input_cells: HashMap<InputCellId, InputCell<T>>,
compute_cells: HashMap<ComputeCellId, ComputeCell<T>>,
next_input_id: usize,
next_compute_id: usize,
next_callback_id: usize,
}
// 单元信息
pub struct CellInfo {
pub id: CellId,
pub dependencies: Vec<CellId>,
pub reverse_dependencies: Vec<CellId>,
}
impl<T: Copy + PartialEq> Reactor<T> {
pub fn new() -> Self {
Reactor {
input_cells: HashMap::new(),
compute_cells: HashMap::new(),
next_input_id: 0,
next_compute_id: 0,
next_callback_id: 0,
}
}
pub fn create_input(&mut self, initial: T) -> InputCellId {
let id = InputCellId(self.next_input_id);
self.next_input_id += 1;
self.input_cells.insert(id, InputCell { value: initial });
id
}
pub fn create_compute<F: Fn(&[T]) -> T + 'static>(
&mut self,
dependencies: &[CellId],
compute_func: F,
) -> Result<ComputeCellId, CellId> {
for &dep in dependencies {
match dep {
CellId::Input(id) => {
if !self.input_cells.contains_key(&id) {
return Err(CellId::Input(id));
}
}
CellId::Compute(id) => {
if !self.compute_cells.contains_key(&id) {
return Err(CellId::Compute(id));
}
}
}
}
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let initial_value = compute_func(&values);
let id = ComputeCellId(self.next_compute_id);
self.next_compute_id += 1;
for &dep in dependencies {
if let CellId::Compute(dep_id) = dep {
if let Some(dep_cell) = self.compute_cells.get_mut(&dep_id) {
if !dep_cell.reverse_dependencies.contains(&id) {
dep_cell.reverse_dependencies.push(id);
}
}
}
}
let compute_cell = ComputeCell {
dependencies: dependencies.to_vec(),
reverse_dependencies: Vec::new(),
compute_func: Box::new(compute_func),
value: initial_value,
callbacks: HashMap::new(),
};
self.compute_cells.insert(id, compute_cell);
Ok(id)
}
pub fn value(&self, id: CellId) -> Option<T> {
match id {
CellId::Input(id) => self.input_cells.get(&id).map(|cell| cell.value),
CellId::Compute(id) => self.compute_cells.get(&id).map(|cell| cell.value),
}
}
pub fn set_value(&mut self, id: InputCellId, new_value: T) -> bool {
let cell = match self.input_cells.get_mut(&id) {
Some(cell) => cell,
None => return false,
};
if cell.value == new_value {
return true;
}
cell.value = new_value;
self.update_dependents(CellId::Input(id));
true
}
pub fn add_callback<F: FnMut(T) + 'static>(
&mut self,
id: ComputeCellId,
callback: F,
) -> Option<CallbackId> {
let cell = self.compute_cells.get_mut(&id)?;
let callback_id = CallbackId(self.next_callback_id);
self.next_callback_id += 1;
cell.callbacks.insert(callback_id, Box::new(callback));
Some(callback_id)
}
pub fn remove_callback(
&mut self,
cell_id: ComputeCellId,
callback_id: CallbackId,
) -> Result<(), RemoveCallbackError> {
let cell = self
.compute_cells
.get_mut(&cell_id)
.ok_or(RemoveCallbackError::NonexistentCell)?;
if cell.callbacks.remove(&callback_id).is_some() {
Ok(())
} else {
Err(RemoveCallbackError::NonexistentCallback)
}
}
// 获取单元信息
pub fn cell_info(&self, id: CellId) -> Option<CellInfo> {
match id {
CellId::Input(input_id) => {
if self.input_cells.contains_key(&input_id) {
Some(CellInfo {
id,
dependencies: vec![],
reverse_dependencies: self.compute_cells
.iter()
.filter(|(_, cell)| cell.dependencies.contains(&id))
.map(|(&compute_id, _)| CellId::Compute(compute_id))
.collect(),
})
} else {
None
}
}
CellId::Compute(compute_id) => {
if let Some(cell) = self.compute_cells.get(&compute_id) {
Some(CellInfo {
id,
dependencies: cell.dependencies.clone(),
reverse_dependencies: cell.reverse_dependencies
.iter()
.map(|&id| CellId::Compute(id))
.collect(),
})
} else {
None
}
}
}
}
// 获取所有单元
pub fn all_cells(&self) -> Vec<CellId> {
let mut cells = Vec::new();
cells.extend(self.input_cells.keys().map(|&id| CellId::Input(id)));
cells.extend(self.compute_cells.keys().map(|&id| CellId::Compute(id)));
cells
}
// 获取依赖图
pub fn dependency_graph(&self) -> HashMap<CellId, Vec<CellId>> {
let mut graph = HashMap::new();
// 添加输入单元的依赖(空)
for &id in self.input_cells.keys() {
graph.insert(CellId::Input(id), vec![]);
}
// 添加计算单元的依赖
for (&id, cell) in &self.compute_cells {
graph.insert(CellId::Compute(id), cell.dependencies.clone());
}
graph
}
fn update_dependents(&mut self, changed_cell: CellId) {
let mut affected_cells = Vec::new();
match changed_cell {
CellId::Input(_) => {
for (&id, cell) in &self.compute_cells {
if cell.dependencies.contains(&changed_cell) {
affected_cells.push(id);
}
}
}
CellId::Compute(id) => {
if let Some(cell) = self.compute_cells.get(&id) {
affected_cells.extend(&cell.reverse_dependencies);
}
}
}
self.update_compute_cells(affected_cells);
}
fn update_compute_cells(&mut self, mut cells_to_update: Vec<ComputeCellId>) {
let mut visited = std::collections::HashSet::new();
while let Some(id) = cells_to_update.pop() {
if !visited.insert(id) {
continue;
}
if let Some(cell) = self.compute_cells.get(&id) {
let dependencies = cell.dependencies.clone();
let compute_func = &cell.compute_func;
let values: Vec<T> = dependencies
.iter()
.map(|&dep| self.value(dep).unwrap())
.collect();
let new_value = compute_func(&values);
if let Some(cell) = self.compute_cells.get_mut(&id) {
let old_value = cell.value;
cell.value = new_value;
if old_value != new_value {
let callback_ids: Vec<CallbackId> = cell.callbacks.keys().cloned().collect();
let mut callbacks_to_call = Vec::new();
for callback_id in callback_ids {
if let Some(callback) = cell.callbacks.get_mut(&callback_id) {
let callback = std::mem::replace(callback, Box::new(|_| {}));
callbacks_to_call.push((callback_id, callback, new_value));
}
}
for (callback_id, mut callback, value) in callbacks_to_call {
callback(value);
cell.callbacks.insert(callback_id, callback);
}
cells_to_update.extend(&cell.reverse_dependencies);
}
}
}
}
}
}
// 响应式系统监视器
pub struct ReactorMonitor<'a, T: Copy + PartialEq> {
reactor: &'a Reactor<T>,
}
impl<'a, T: Copy + PartialEq> ReactorMonitor<'a, T> {
pub fn new(reactor: &'a Reactor<T>) -> Self {
ReactorMonitor { reactor }
}
// 监视单元值的变化
pub fn watch_value(&self, id: CellId) -> Option<T> {
self.reactor.value(id)
}
// 获取单元的依赖信息
pub fn get_dependencies(&self, id: CellId) -> Option<Vec<CellId>> {
match id {
CellId::Input(_) => Some(vec![]),
CellId::Compute(id) => {
self.reactor.compute_cells
.get(&id)
.map(|cell| cell.dependencies.clone())
}
}
}
}
// 便利函数
pub fn create_reactor<T: Copy + PartialEq>() -> Reactor<T> {
Reactor::new()
}
pub fn link_cells<T: Copy + PartialEq>(
reactor: &mut Reactor<T>,
source: CellId,
target: ComputeCellId,
) -> bool {
if let Some(cell_info) = reactor.cell_info(source) {
// 这只是一个示例,实际实现会更复杂
true
} else {
false
}
}
实际应用场景
响应式编程在实际开发中有以下应用:
- 前端框架:ReactJS、Vue.js等现代前端框架的状态管理
- 游戏开发:游戏状态管理和UI更新
- 数据可视化:实时数据图表和仪表板
- 金融系统:实时价格更新和风险计算
- 物联网:传感器数据处理和设备控制
- 科学计算:数值模拟和数据分析
- GUI应用:桌面应用的用户界面更新
- 实时系统:需要快速响应数据变化的系统
算法复杂度分析
-
时间复杂度:
- 创建单元:O(1)
- 设置值:O(D),其中D是依赖深度
- 获取值:O(1)
- 添加回调:O(1)
-
空间复杂度:O(N)
- 其中N是单元总数,需要存储所有单元及其依赖关系
与其他实现方式的比较
rust
// 使用观察者模式的实现
pub trait Observer<T> {
fn update(&mut self, value: T);
}
pub trait Subject<T> {
fn attach(&mut self, observer: Box<dyn Observer<T>>);
fn detach(&mut self, observer: &Box<dyn Observer<T>>);
fn notify(&self, value: T);
}
// 使用流处理的实现
// [dependencies]
// futures = "0.3"
use futures::stream::Stream;
pub struct ReactiveStream<T> {
// 基于流的响应式实现
}
// 使用第三方库的实现
// [dependencies]
// rxrust = "1.0"
// 使用RxRust的响应式实现
/*
use rxrust::prelude::*;
pub struct RxReactor<T> {
// 基于RxRust的实现
}
*/
// 使用宏的实现
macro_rules! reactive_cell {
($name:ident, $value:expr) => {
// 宏实现的响应式单元
};
}
// 使用函数式响应式编程的实现
pub struct FRPReactor<T> {
// 函数式响应式编程实现
}
总结
通过 react 练习,我们学到了:
- 响应式编程:掌握了响应式编程的核心概念和实现方式
- 所有权系统:深入理解了Rust的所有权和借用机制
- 闭包处理:学会了存储和调用可变闭包
- 数据结构设计:理解了如何设计复杂的数据结构来管理依赖关系
- 变化传播:学会了实现值变化的自动传播机制
- 类型安全:通过不同的ID类型确保了类型安全
这些技能在实际开发中非常有用,特别是在前端框架、状态管理、实时系统等场景中。响应式编程虽然是一个复杂的概念,但它涉及到了所有权、闭包、数据结构设计、变化检测等许多核心概念,是学习Rust高级编程的良好起点。
通过这个练习,我们也看到了Rust在实现复杂系统方面的强大能力,以及如何用安全且高效的方式实现响应式编程模式。这种结合了安全性和性能的语言特性正是Rust的魅力所在。