mirror of
https://github.com/posborne/rust-pstree.git
synced 2024-12-27 07:26:03 +01:00
Add Readme
This commit is contained in:
parent
9acf97f949
commit
4053023568
1 changed files with 161 additions and 0 deletions
161
README.md
Normal file
161
README.md
Normal file
|
@ -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<ProcessRecord>) {
|
||||||
|
// 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!
|
||||||
|
|
Loading…
Reference in a new issue