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!