-
Notifications
You must be signed in to change notification settings - Fork 0
Transcription notes from rust patterns talk in suwon
- l: talk about common rust patterns, specifically ones in servo. things we use to get around issues with ownership, or enforce safe static patterns
- l: today enforce a safe interface for an unsafe doubly linked list impl. second will talk about phantom types
- l: linked list with owned pointers
- l: owned boxes are allocated globally, not allocated in a task, but only have one owner
- l: when x is assigned to y, x no longer has ownership. this trips people up on the mailing list
- l: a linked list is an ordered sequence of nodes, each node has pointers
-l: list parameterized over T, head points to None, or Some(~Node). The reason it has to be a pointer and not just Some(Node) is because Rust would lay them out inline.
- l: if we try to 'instantiate' the type, the compiler will overflow because when it tries to exand the type it will expand to Node<Node<Node<....>>> , infinitely. The pointer allows the type to have a known size.
- l: when you want a doublly linked list you would like to have a pointer that goes the other way (backward) through the list, but because we're using owned pointers, you're going to have...
-l: when you try to push an element on the front of the list you're going to run into a bunch of type errors. In the None case there are no elements in the list, so you take the head element and move it to the tail of the list, but the owner is the tail of the list, so when you try to ... The problem is you have two different owners of the same object.
- l: instead of using owned pointers, we use an unsafe pointers. We want to provide a safe interface on top of these unsafe pointers. Whenever you use unsafe pointers you want to maintain the invariant that you know the object is still owned by someone. Whenever releasing ownership of an object you release the pointer.
- l: the way we do this in extra::DList is to create a struct that wraps the raw pointer, Rawlink. It contains a bunch of convenient accessors.
- pradeep: can you go to the last point on the previous slide about the invariaent?
- l: the invariant says 'if you are going to use raw pointers you need to know who still has ownership'. also that you don't leak the raw pointer. unfortunately this is a measure of dynamic safety, so the compile doesn't help you. if you do it wrong you'll just crash.
- l: so you can use this like an option of an owned pointer. None is just a wrapper around null, and Some pulls out the unsafe pointer.
- l: So now when we implement the new version of push_front instead of using Option hwe use Rawlink instead, wrapping it in some. Important thing is that we don't change the ownership of the new_head, all the
prev
pointers are Rawlinks that contain an unsafe pointer, don't have ownership.
-
l: to use this value you use unsafe code.
-
l: convert pointer to a normal Option to get back to the underlying &T type. one of the motivation for having ....
-
l: this looks very similar to ...
-
jack: so p.to_option does a dynamic check if it's null, but we still have to maintain that that's valid memory?
-
l: yes. the dynamic check isn't suffecient to know whether it's valid, you still have to maintain the ownership.
- l: you have to be careful exposing Rawlink. Here's an example. because the compiler isn't checking this code ... 'z' is a Rawlink wrapper around ~3. When we call resolve_imute we pull out an Option type that contains a pointer to the owner pointer. If we dereference it after the owned pointer is destroyed then we crash.
- l: the thing you have to be careful of when using raw pointer is to make sure you don't leak out an object wrapping a raw pointer - the compiler won't check, you have to prove it dynamically.
- l: useful when you have some sort of data strucutre using owned types where you need multiple pointers into the graph - alternative is rewalking the list, tree, etc. the key is to not expose the underlying pointer except for when it's guaranteed to be valid.
- l: flow tree is owned by the layout task. for incremental layout the DOM needs pointers into flow tree so they know corresponding elements. invariant: if DOM objects have pointers into these that will only be the case when the layout task is holding onto the flow tree. if we ever update the flow tree we only update pointers in the original DOM (?).
- l: questions?
- ryan: can you go back a few slides, to "Use by converto to an Option". that gives an immutable type.
- l: Other pattern: phantom types: any type whose type parameter is not used. This Typename is parameterized over Unused but noned of the fields use it. Why? Several reasons. In servo we use it to partition the methods of a data structure; some callers can access 1 set of methods, others can access another set of methods.
- l: Can't use 'priv' - that's only within one unit of compilation, can't use visibility info, need to use type system.
- l: Partition out the 'secure' piece of info, move all the Secure methods into a different impl that's only available when Shared is instantiated over the Secure type.
- l: when entering code that should have access to Secure methods, e.g. if data has been validated; in Servo case we might be transitioning from Script to Layout task. change the type of the object. the naming of phantom type lets you know what context you are working in, e.g. a Node shouldn't be accessed from layout task.
- l: malicious coders can't be prevented from circumventing system. there's no compiler check that...
- l: servo uses this to seperate the view that script has of DOM nodes from the view that layout has. in ScriptView you can mutate nodes but not layout data. in LayoutView layout has access to layout data and can't mutate nodes.
- l: Small trick. You'll run into it a lot in Rust code. Also used in Haskell for tracking I/O effects, cleaning and tracking strings - changing them from unescaped to escaped or sanitized strings.