diff --git a/README.md b/README.md new file mode 100644 index 0000000..6eb03d9 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +pstree in Rust +============== + +This repository contains a simple implementation of the Unix pstree +program in Rust. This is mostly done as a learning exercise and port +of a [C implementation I +wrote](https://github.com/posborne/linux-programming-interface-exercises/blob/6b1ae2357d7c73378a56e2d7b499b4ab49c4452f/12-system-and-process-information/pstree.c) +as an exercise in the Excellent book [The Linux Programming Interface +(TLPI)](http://man7.org/tlpi/). + +The implementation here does not and is not intended to fully match +the implementation of pstree that you might find on your computer and +is not intended to replace it. + +Build and Run It +----------------- + +You will need to install the latest version of rust. When I +originally wrote this code, v0.12 of Rust had just recently been +released. + + $ rustc pstree.rs + $ ./pstree + + +Notes From Implementing +----------------------- + +As noted, implementing this program in Rust was very much a learning +exercise and a somewhat painful one at that. Here's some useful +things I picked up along the way that might be helpful for other +aspiring Rustafarians. + +### The Compiler is Probably Right + +There are things you can get away with in languages like C that are +either unsafe or not provably safe. Rust is a safe language (by +default) and it is useful to try to think in terms of the compiler +being able to reason about your code. + +With rust-pstree, one thing that I found I was not thinking deep +enough was ownership of data associated with each process. Rust is +not a managed language, so it must be clear who owns and what the +lifetime of each piece of memory is for your code to compile. This +takes some getting used to but starts to make sense over time. + +As my work progressed, I started to be able to ask the right +questions. This yielded much better results when asking for help on +IRC. For instance, consider the following function: + +```rust +fn populate_node(node : &mut ProcessTreeNode, records: &Vec) { + // populate the node by finding its children... recursively + let pid = node.record.pid; // avoid binding node as immutable in closure + for record in records.iter().filter(|record| record.ppid == pid) { + let mut child = ProcessTreeNode::new(record); + populate_node(&mut child, records); + node.children.push(child); + } +} +``` + +At first, a lot of things look weird here. Now, I can look at this +and read through the code making the following obervations: + +1. There is a function called `populate_node` that takes a mutable borrowed + reference to a `ProcessTreeNode` and an immutable borrowed + reference to a `Vec` of `ProccesRecord`s and returns nothing. From + this I know that the provided node may change but nothing else will + including elements within the records vector. + +2. First, there is an odd line with a comment. It says `avoid binding + node as immutable in closure`. This change was made because we + needed to make reference to the node's `pid` within the for loop + but the compiler was preventing us from doing so through the node + within the closure for the `filter` expression. + + Without this change, we receive the following error: + ``` + pstree.rs:121:9: 121:22 error: cannot borrow `node.children` as mutable because `node` is also borrowed as immutable + pstree.rs:121 node.children.push(child); + ^~~~~~~~~~~~~ + pstree.rs:118:41: 118:80 note: previous borrow of `node` occurs here due to use in closure; the immutable borrow prevents subsequent moves or mutable borrows of `node` until the borrow ends + pstree.rs:118 for record in records.iter().filter(|record| record.ppid == node.record.pid) { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + pstree.rs:123:2: 123:2 note: previous borrow ends here + pstree.rs:118 for record in records.iter().filter(|record| record.ppid == node.record.pid) { + pstree.rs:119 let mut child = ProcessTreeNode::new(record); + pstree.rs:120 populate_node(&mut child, records); + pstree.rs:121 node.children.push(child); + pstree.rs:122 } + pstree.rs:123 } + ^ + ``` + + Well, that looks pretty confusing. The 3rd line of the error, + however, gives us a key insight. Node is first borrowed as + immutable implicitly by the closure and Rust's lexical scoping + rules. The life of that closure will be to the end of the for loop + block. This would usually not be a problem, but accessing the + record of the node involves borrowing a reference to the node + children. Since `node` is mutable in this function, we are trying + to obtain a mutable borrow within a closure where node is borrowed + as immutable. As this violates the immutability constraint, the + compiler balks. + + We fix by storing an immutable copy of the pid prior to the + creation of the closure. Problem. solved. All I can say, is that + you get much better at reading the compiler's errors and fixing + them quickly as you go on. + +3. The rest of the function (in the body of the for loop), reads + pretty clearly. We have filtered all the records so that we are + iterating over those records who are children of the node we are + populating. We create a new node for each of these children and + populate it (recursive call). Finally, we push the child into our + list, taking ownership of it (e.g. The vec owns the child, the Node + owns the vec, the tree owns the root node, and some scope (stack) + owns the Tree. It is pretty much what you would do in C/C++, but + the compiler checks _much_ more for you, ensuring greater safety. + +### The Best way to Learn is by Reading Other Code + +At several points while implementing, I got stuck. I found that +reading other code in the Rust standard library and in the community +often led to breakthroughs in my thinking. Some of this was just +noticing a standard library function I had missed before (Oh, there is +an `iter_mut()` as well as an `iter()` on `Vec`? Other times, it was +just picking up the 'rusty' way of structuring a program. + +I can't claim to be an expert or to have generated anything good, but +reading is learning. Also, viewing the change for some files over +time and the related PRs/discussion can be very interesting and +enlightening. + +### Use IRC + +I got good help when really stuck on the IRC channel. Just have a +clear question ready to ask and code snippets ready to go. The +community there wants to see you succeed. + +Final Thoughts +-------------- + +This first experience with Rust has been somewhat frustrating but also +very enlightening. My optimism that Rust could represent a real +threat to entrenched systems programming languages is not crushed. +The next few months as Rust moves toward 1.0 will be very +interesting. I hope that the number of symbols in the language does +not increase much more. I already find the two meanings for the `&` +operator to be confusing at times. Explicit is better than implicit +in most cases. + +Rust is not an 'easy' language to learn and I don't think it could be +while still doing what it needs to do. Manual memory management is +not easy, but I believe that Rust provides a real service to +programmers and users by providing a language that greatly increases +the chances that a program that runs is actually safe. I, for one, +have already spent more than enough time debugging race conditions and +resources leaks... Good riddance! +