-
Notifications
You must be signed in to change notification settings - Fork 0
/
rust-result.rs
143 lines (129 loc) · 4.47 KB
/
rust-result.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// In Rust, the `Result` type is used extensively. The problem is that
// using the `match` pattern can make code much more difficult to read.
// The following are several refactors that make the code:
// * more composable
// * easier to read
// * smaller, more reusable functions
// Example 1: Lift `Option` results into a `Result`
// Original
// This function takes a path like:
// /drop-zone/some-file-name.txt
// and adds a sub folder
// /drop-zone/archive/some-file-name.txt
fn modify_path(path: &PathBuf, filename: &OsStr) -> Result<PathBuf, Error> {
match path.parent() {
Some(parent) => Ok(parent.join("archive").join(filename)),
None => Err(Error(format!(
"Failed to create archive folder path: {:?}",
path
))),
}
}
// Result
// When we separate the 'system' code from the 'business logic'
// it results in two functions which are both easier to understand
// and easier to reuse.
fn modify_path(path: &PathBuf, filename: &OsStr) -> Result<PathBuf, Error> {
let parent = path_parent(path)?;
Ok(parent.join("archive").join(filename))
}
fn path_parent(path: &PathBuf) -> Result<PathBuf, Error> {
match path.parent() {
Some(parent) => Ok(parent.to_owned()),
None => Err(Error(format!(
"Path terminates in a root or prefix. Path: {:?}",
path
))),
}
}
// Example 2:
// This was a nasty bit of code because it was simple but didnt look like it.
// It had similar problems as the first example where there was a combination
// of `Option` and `Result`.
fn get_file(path: &PathBuf) -> Result<FileRef, Error> {
match path.file_name() {
Some(osname) => match osname.to_str() {
Some(filename) => match get_archive_path(path, osname) {
Ok(archive_path) => Ok(FileRef {
filename: filename.to_string(),
path: path.to_path_buf(),
archive_path: archive_path,
}),
Err(error) => Err(error),
},
None => Err(Error(format!(
"Could not process file. Failed to check for UTF-8 validity: {}",
path.display()
))),
},
None => Err(Error(format!(
"Could not process file. path terminates in ...: {}",
path.display()
))),
}
}
// First Pass
// First, lets separate out the 'system' logic of getting
// a file name as string, into a `file_name` function.
// This helps us because:
// * the `file_name` function is now reusable
// * the `get_file` function is simpler and 'business logic-y'
// * we now have a normalized return type of `Result`.
fn get_file(path: &PathBuf) -> Result<FileRef, Error> {
match file_name(path) {
Ok((filename, osname)) =>
match get_archive_path(path, osname) {
Ok(archive_path) => Ok(FileRef {
filename: filename.to_string(),
path: path.to_path_buf(),
archive_path: archive_path,
}),
Err(error) => Err(error),
},
Err(error) => Err(error)
}
}
fn file_name(path: &PathBuf) -> Result<(String, &OsStr), Error> {
match path.file_name() {
Some(osname) => match osname.to_str() {
Some(filename) => Ok((filename.to_string(), osname)),
None => Err(Error(format!(
"Could not process file. Failed to check for UTF-8 validity: {}",
path.display()
))),
},
None => Err(Error(format!(
"Could not process file. path terminates in ...: {}",
path.display()
))),
}
}
// Second Pass on `get_file`
// This was actually **unsuccessful**, but I wnated to show it anyways.
// This shows the use of the `and_then` syntax (read: monad in Haskell).
// But it doesn't work because of how the `file_name` function
// returns the `filename` and `osname`, and the `filename` needs
// to be in scope in the second `and_then` lambda.
fn get_file(path: &PathBuf) -> Result<FileRef, Error> {
file_name(path)
.and_then(|(filename, osname)| get_archive_path(path, osname))
.and_then(|archive_path|
Ok(FileRef { //filename isn't in scope here
filename: filename.to_string(),
path: path.to_path_buf(),
archive_path: archive_path,
}))
}
//Third Pass
// So, lets use our `?` syntax again (`?` is like `do` notation in Haskell)
// This significantly simplifies what is happening here.
// Compare this to our original function!
fn get_file(path: &PathBuf) -> Result<FileRef, Error> {
let (filename, osname) = file_name(path)?;
let archive_path = get_archive_path(path, osname)?;
Ok(FileRef {
filename: filename.to_string(),
path: path.to_path_buf(),
archive_path: archive_path,
})
}