Skip to content

Commit 2711a07

Browse files
authored
Merge pull request #464 from petertseng/react-errs
react: return domain-specific errors
2 parents 3c64502 + 36354b4 commit 2711a07

File tree

3 files changed

+66
-42
lines changed

3 files changed

+66
-42
lines changed

exercises/react/example.rs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ use std::collections::HashMap;
33
pub type CellID = usize;
44
pub type CallbackID = usize;
55

6+
#[derive(Debug, PartialEq)]
7+
pub enum SetValueError {
8+
NonexistentCell,
9+
ComputeCell,
10+
}
11+
12+
#[derive(Debug, PartialEq)]
13+
pub enum RemoveCallbackError {
14+
NonexistentCell,
15+
NonexistentCallback,
16+
}
17+
618
struct Cell<'a, T: Copy> {
719
value: T,
820
last_value: T,
@@ -46,11 +58,11 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
4658
self.cells.len() - 1
4759
}
4860

49-
pub fn create_compute<F: Fn(&[T]) -> T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<CellID, &'static str> {
61+
pub fn create_compute<F: Fn(&[T]) -> T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<CellID, CellID> {
5062
// Check all dependencies' validity before modifying any of them,
5163
// so that we don't perform an incorrect partial write.
52-
if !dependencies.iter().all(|&id| id < self.cells.len()) {
53-
return Err("Nonexistent input");
64+
if let Some(&invalid) = dependencies.iter().find(|&dep| *dep >= self.cells.len()) {
65+
return Err(invalid);
5466
}
5567
let new_id = self.cells.len();
5668
for &id in dependencies {
@@ -66,16 +78,16 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
6678
self.cells.get(id).map(|c| c.value)
6779
}
6880

69-
pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), &'static str> {
81+
pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), SetValueError> {
7082
match self.cells.get_mut(id) {
7183
Some(c) => match c.cell_type {
7284
CellType::Input => {
7385
c.value = new_value;
7486
Ok(c.dependents.clone())
7587
},
76-
CellType::Compute(_, _) => Err("Can't set compute cell value directly"),
88+
CellType::Compute(_, _) => Err(SetValueError::ComputeCell),
7789
},
78-
None => Err("Can't set nonexistent cell"),
90+
None => Err(SetValueError::NonexistentCell),
7991
}.map(|deps| {
8092
for &d in deps.iter() {
8193
self.update_dependent(d);
@@ -88,24 +100,21 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
88100
})
89101
}
90102

91-
pub fn add_callback<F: FnMut(T) -> () + 'a>(&mut self, id: CellID, callback: F) -> Result<CallbackID, &'static str> {
92-
match self.cells.get_mut(id) {
93-
Some(c) => {
94-
c.callbacks_issued += 1;
95-
c.callbacks.insert(c.callbacks_issued, Box::new(callback));
96-
Ok(c.callbacks_issued)
97-
},
98-
None => Err("Can't add callback to nonexistent cell"),
99-
}
103+
pub fn add_callback<F: FnMut(T) -> () + 'a>(&mut self, id: CellID, callback: F) -> Option<CallbackID> {
104+
self.cells.get_mut(id).map(|c| {
105+
c.callbacks_issued += 1;
106+
c.callbacks.insert(c.callbacks_issued, Box::new(callback));
107+
c.callbacks_issued
108+
})
100109
}
101110

102-
pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), &'static str> {
111+
pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), RemoveCallbackError> {
103112
match self.cells.get_mut(cell) {
104113
Some(c) => match c.callbacks.remove(&callback) {
105114
Some(_) => Ok(()),
106-
None => Err("Can't remove nonexistent callback"),
115+
None => Err(RemoveCallbackError::NonexistentCallback),
107116
},
108-
None => Err("Can't remove callback from nonexistent cell"),
117+
None => Err(RemoveCallbackError::NonexistentCell),
109118
}
110119
}
111120

exercises/react/src/lib.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
pub type CellID = ();
66
pub type CallbackID = ();
77

8+
#[derive(Debug, PartialEq)]
9+
pub enum SetValueError {
10+
NonexistentCell,
11+
ComputeCell,
12+
}
13+
14+
#[derive(Debug, PartialEq)]
15+
pub enum RemoveCallbackError {
16+
NonexistentCell,
17+
NonexistentCallback,
18+
}
19+
820
pub struct Reactor<T> {
921
// Just so that the compiler doesn't complain about an unused type parameter.
1022
// You probably want to delete this field.
@@ -28,12 +40,14 @@ impl <T: Copy + PartialEq> Reactor<T> {
2840
// You do not need to reject compute functions that expect more arguments than there are
2941
// dependencies (how would you check for this, anyway?).
3042
//
31-
// Return an Err (and you can change the error type) if any dependency doesn't exist.
43+
// If any dependency doesn't exist, returns an Err with that nonexistent dependency.
44+
// (If multiple dependencies do not exist, exactly which one is returned is not defined and
45+
// will not be tested)
3246
//
3347
// Notice that there is no way to *remove* a cell.
3448
// This means that you may assume, without checking, that if the dependencies exist at creation
3549
// time they will continue to exist as long as the Reactor exists.
36-
pub fn create_compute<F: Fn(&[T]) -> T>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<CellID, ()> {
50+
pub fn create_compute<F: Fn(&[T]) -> T>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<CellID, CellID> {
3751
unimplemented!()
3852
}
3953

@@ -50,20 +64,22 @@ impl <T: Copy + PartialEq> Reactor<T> {
5064

5165
// Sets the value of the specified input cell.
5266
//
53-
// Return an Err (and you can change the error type) if the cell does not exist, or the
54-
// specified cell is a compute cell, since compute cells cannot have their values directly set.
67+
// Returns an Err if either:
68+
// * the cell does not exist
69+
// * the specified cell is a compute cell, since compute cells cannot have their values
70+
// directly set.
5571
//
5672
// Similarly, you may wonder about `get_mut(&mut self, id: CellID) -> Option<&mut Cell>`, with
5773
// a `set_value(&mut self, new_value: T)` method on `Cell`.
5874
//
5975
// As before, that turned out to add too much extra complexity.
60-
pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), ()> {
76+
pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), SetValueError> {
6177
unimplemented!()
6278
}
6379

6480
// Adds a callback to the specified compute cell.
6581
//
66-
// Return an Err (and you can change the error type) if the cell does not exist.
82+
// Returns the ID of the just-added callback, or None if the cell doesn't exist.
6783
//
6884
// Callbacks on input cells will not be tested.
6985
//
@@ -73,17 +89,16 @@ impl <T: Copy + PartialEq> Reactor<T> {
7389
// * Exactly once if the compute cell's value changed as a result of the set_value call.
7490
// The value passed to the callback should be the final value of the compute cell after the
7591
// set_value call.
76-
pub fn add_callback<F: FnMut(T) -> ()>(&mut self, id: CellID, callback: F) -> Result<CallbackID, ()> {
92+
pub fn add_callback<F: FnMut(T) -> ()>(&mut self, id: CellID, callback: F) -> Option<CallbackID> {
7793
unimplemented!()
7894
}
7995

8096
// Removes the specified callback, using an ID returned from add_callback.
8197
//
82-
// Return an Err (and you can change the error type) if either the cell or callback
83-
// does not exist.
98+
// Returns an Err if either the cell or callback does not exist.
8499
//
85100
// A removed callback should no longer be called.
86-
pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), ()> {
101+
pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), RemoveCallbackError> {
87102
unimplemented!()
88103
}
89104
}

exercises/react/tests/react.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn an_input_cells_value_can_be_set() {
2323
fn error_setting_a_nonexistent_input_cell() {
2424
let mut dummy_reactor = Reactor::new();
2525
let input = dummy_reactor.create_input(1);
26-
assert!(Reactor::new().set_value(input, 0).is_err());
26+
assert_eq!(Reactor::new().set_value(input, 0), Err(SetValueError::NonexistentCell));
2727
}
2828

2929
#[test]
@@ -50,7 +50,7 @@ fn compute_cells_take_inputs_in_the_right_order() {
5050
fn error_creating_compute_cell_if_input_doesnt_exist() {
5151
let mut dummy_reactor = Reactor::new();
5252
let input = dummy_reactor.create_input(1);
53-
assert!(Reactor::new().create_compute(&[input], |_| 0).is_err());
53+
assert_eq!(Reactor::new().create_compute(&[input], |_| 0), Err(input));
5454
}
5555

5656
#[test]
@@ -61,7 +61,7 @@ fn do_not_break_cell_if_creating_compute_cell_with_valid_and_invalid_input() {
6161
let dummy_cell = dummy_reactor.create_input(2);
6262
let mut reactor = Reactor::new();
6363
let input = reactor.create_input(1);
64-
assert!(reactor.create_compute(&[input, dummy_cell], |_| 0).is_err());
64+
assert_eq!(reactor.create_compute(&[input, dummy_cell], |_| 0), Err(dummy_cell));
6565
assert!(reactor.set_value(input, 5).is_ok());
6666
assert_eq!(reactor.value(input), Some(5));
6767
}
@@ -96,7 +96,7 @@ fn error_setting_a_compute_cell() {
9696
let mut reactor = Reactor::new();
9797
let input = reactor.create_input(1);
9898
let output = reactor.create_compute(&[input], |_| 0).unwrap();
99-
assert!(reactor.set_value(output, 3).is_err());
99+
assert_eq!(reactor.set_value(output, 3), Err(SetValueError::ComputeCell));
100100
}
101101

102102
/// A CallbackRecorder helps tests whether callbacks get called correctly.
@@ -139,7 +139,7 @@ fn compute_cells_fire_callbacks() {
139139
let mut reactor = Reactor::new();
140140
let input = reactor.create_input(1);
141141
let output = reactor.create_compute(&[input], |v| v[0] + 1).unwrap();
142-
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_ok());
142+
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_some());
143143
assert!(reactor.set_value(input, 3).is_ok());
144144
cb.expect_to_have_been_called_with(4);
145145
}
@@ -150,7 +150,7 @@ fn error_adding_callback_to_nonexistent_cell() {
150150
let mut dummy_reactor = Reactor::new();
151151
let input = dummy_reactor.create_input(1);
152152
let output = dummy_reactor.create_compute(&[input], |_| 0).unwrap();
153-
assert!(Reactor::new().add_callback(output, |_: usize| println!("hi")).is_err());
153+
assert_eq!(Reactor::new().add_callback(output, |_: usize| println!("hi")), None);
154154
}
155155

156156
#[test]
@@ -160,7 +160,7 @@ fn callbacks_only_fire_on_change() {
160160
let mut reactor = Reactor::new();
161161
let input = reactor.create_input(1);
162162
let output = reactor.create_compute(&[input], |v| if v[0] < 3 { 111 } else { 222 }).unwrap();
163-
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_ok());
163+
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_some());
164164

165165
assert!(reactor.set_value(input, 2).is_ok());
166166
cb.expect_not_to_have_been_called();
@@ -180,14 +180,14 @@ fn callbacks_can_be_added_and_removed() {
180180
let output = reactor.create_compute(&[input], |v| v[0] + 1).unwrap();
181181

182182
let callback = reactor.add_callback(output, |v| cb1.callback_called(v)).unwrap();
183-
assert!(reactor.add_callback(output, |v| cb2.callback_called(v)).is_ok());
183+
assert!(reactor.add_callback(output, |v| cb2.callback_called(v)).is_some());
184184

185185
assert!(reactor.set_value(input, 31).is_ok());
186186
cb1.expect_to_have_been_called_with(32);
187187
cb2.expect_to_have_been_called_with(32);
188188

189189
assert!(reactor.remove_callback(output, callback).is_ok());
190-
assert!(reactor.add_callback(output, |v| cb3.callback_called(v)).is_ok());
190+
assert!(reactor.add_callback(output, |v| cb3.callback_called(v)).is_some());
191191

192192
assert!(reactor.set_value(input, 41).is_ok());
193193
cb1.expect_not_to_have_been_called();
@@ -205,11 +205,11 @@ fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() {
205205
let input = reactor.create_input(1);
206206
let output = reactor.create_compute(&[input], |v| v[0] + 1).unwrap();
207207
let callback = reactor.add_callback(output, |v| cb1.callback_called(v)).unwrap();
208-
assert!(reactor.add_callback(output, |v| cb2.callback_called(v)).is_ok());
209-
// We want the first remove to be Ok, but we don't care about the others.
208+
assert!(reactor.add_callback(output, |v| cb2.callback_called(v)).is_some());
209+
// We want the first remove to be Ok, but the others should be errors.
210210
assert!(reactor.remove_callback(output, callback).is_ok());
211211
for _ in 1..5 {
212-
assert!(reactor.remove_callback(output, callback).is_err());
212+
assert_eq!(reactor.remove_callback(output, callback), Err(RemoveCallbackError::NonexistentCallback));
213213
}
214214

215215
assert!(reactor.set_value(input, 2).is_ok());
@@ -227,7 +227,7 @@ fn callbacks_should_only_be_called_once_even_if_multiple_dependencies_change() {
227227
let minus_one1 = reactor.create_compute(&[input], |v| v[0] - 1).unwrap();
228228
let minus_one2 = reactor.create_compute(&[minus_one1], |v| v[0] - 1).unwrap();
229229
let output = reactor.create_compute(&[plus_one, minus_one2], |v| v[0] * v[1]).unwrap();
230-
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_ok());
230+
assert!(reactor.add_callback(output, |v| cb.callback_called(v)).is_some());
231231
assert!(reactor.set_value(input, 4).is_ok());
232232
cb.expect_to_have_been_called_with(10);
233233
}
@@ -241,7 +241,7 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt
241241
let plus_one = reactor.create_compute(&[input], |v| v[0] + 1).unwrap();
242242
let minus_one = reactor.create_compute(&[input], |v| v[0] - 1).unwrap();
243243
let always_two = reactor.create_compute(&[plus_one, minus_one], |v| v[0] - v[1]).unwrap();
244-
assert!(reactor.add_callback(always_two, |v| cb.callback_called(v)).is_ok());
244+
assert!(reactor.add_callback(always_two, |v| cb.callback_called(v)).is_some());
245245
for i in 2..5 {
246246
assert!(reactor.set_value(input, i).is_ok());
247247
cb.expect_not_to_have_been_called();

0 commit comments

Comments
 (0)