In rust, you can create a tuple by let tuple = (x0, x1, x2) and access them by tuple.0, tuple.1, and tuple.2. For example:
let tuple = (1, "hello");
assert_eq!(tuple.0, 1);
assert_eq!(tuple.1, "hello");
Is equivalent to the following Python code:
tuple = (1, "hello")
assert tuple[0] == 1
assert tuple[1] == "hello"
A tuple struct is similar to a tuple but the type itself is named:
struct T(i32, String);
let t = T(42, "hello".to_string());
assert_eq!(t.0, 42);
assert_eq!(t.1, "hello");
enum let you express a type that has several variants:
enum Color {
Red,
Green,
Blue,
}
This is similar to enum in C++, the difference is that Rust allows the variants to contain fields, which make it an Algebraic Data Type:
enum T {
A(usize),
B(i32, String),
C { field: String },
}
This is similar to templates in C++. In Rust:
struct MyType<T> {
vec: Vec<T>
}
impl<T> MyType<T> { // notice the location of the type variable
fn new() -> Self {
Self {
vec: vec![],
}
}
}
In C++:
template<typename T>
struct MyType {
vector<T> vec;
static MyType new() {
MyType ret;
ret.vec = vector<T>();
return ret;
}
}
matchUse match to match an enum with different variants. The pattern _ will match anything:
match future.poll() {
Poll::Ready(Some(x)) if x % 2 == 1 => {
f(x);
}
Poll::Ready(_) => return Poll::Pending,
_ => println!("default match"),
}
if let, while letUse if let to go into a branch if the expression matches:
if let Poll::Ready(x) = future.poll() {
return x;
}
There is also while let:
while let Poll::Ready(Some(x)) = stream.poll() {
f(x);
}
// equivalent to
loop {
if let Poll::Ready(Some(x)) = stream.poll() {
f(x);
} else {
break;
}
}
OptionOption<T> is a type that has two variants:
pub enum Option<T> {
None,
Some(T),
}
This is similar to nullable types in other languages (e.g., None in Python or null in Java). The difference is that in Rust, you must explicitly wrap a type inside Option so you can set it to be None.
Some important functions you will use in this project:
unwrap and expect: Assert that the option is Some(x) and return x. Similar to the following code in Python:def unwrap(option):
assert option is not None
return option
is_some, is_none: Determine which variant the option holds. x.is_some() is equivalent to:match x {
Some(_) => true,
None => false,
}
take: Take the option out from a mutable reference to the option and leave a None there. This is extremely useful if you want to take out the value from a mutable reference, temporarily or forever. For example, in Python you might do:resource = self.resource
self.resource = None
do_something(resource)
In rust, you will do
// make self.resource a type of Option<T>
let resource = self.resource.take()
do_something(resource)
as_ref, as_mut: Turn an &Option<T> into Option<&T>. Similar for the mutable case.map, ok_or, or_else, …ResultResult<T, E> is a type that represents a result that could fail. It is a replacement of error handling using catch and throw in other language.
match do_something() {
Ok(result) => return f(result),
Err(err) => {
println!("err = {}", err),
}
}
Which does similar things to the following Python code:
try:
do_something()
except Exception as e:
print('err = {}'.format(e))
Some handy functions:
unwrap, expect: similar to those methods of an Option. Assert the result is an Ok.You can write result? if result is of type Result. The expression is roughly equivalent to
{
match result {
Ok(v) => v,
Err(err) => return v,
}
}
This is useful so you can propagate error by appending results with ?:
let file = open("file")?;
let but = file.read_to_string()?;
The lifetime is where a variable lives or a borrow is valid. In rust, lifetime are represent with 'a, 'b, etc. For example:
{
let x = vec![1, 2, 3];
{
let y = &mut x;
f(y);
// The borrow of y ends here.
}
// x is dropped here.
}
After rust added non-lexical lifetime, the compiler will tweak the lifetime of borrows for you, which makes your life easier, but the lifetime of an object always ends at the end of the scope.
A static lifetime means that the variable can live for the running program’s entire lifetime. It is represented as 'static
BoxBox is a pointer to an allocation on the heap. It is equivalent to unique_ptr in C++:
In Rust:
let x = Box::new(1);
let y = x; // x is moved and cannot be used anymore.
In C++:
std::unique_ptr<int> x = std::make_unique<int>(1);
std::unique_ptr<int> y = std::move(x);
RcRc is a reference-counting pointer. It is equivalent to shared_ptr in C++:
let x = Rc::new(1);
let y = Rc::clone(&x); // x is moved and cannot be used anymore.
In C++:
std::shared_ptr<int> x = std::make_shared<int>(1);
std::shared_ptr<int> y = x;
It is useful when multiple ownership to an object is needed.Rc will drop the object when the last reference goes out of scope. Use Rc::clone to get a shallow copy of the object (it only copies the address and increases the reference by one).
Rc only allows you to dereference into the inner type immutably. Thus, you will typically use Rc<RefCell<T>> so that you can modify T, see the section for RefCell.
For x: Rc<T>, if T has a method f, you can simply call x.f(). Rust will dereference the pointer for you (this works for most smart pointers, including Box, etc.).
ArcArc has the same interface as Rc, the only difference is that Arc is thread-safe and has more overhead. In this project, you only need Rc most of the time because we are running the whole program in a single thread. The only exception is when you are implementing ArcWake, you need to use Arc because Rust imposes a rule that Waker must be thread safe.
Interior mutability allows you to turn an immutable reference into mutable ones, usually in runtime. This is useful when it is hard or impossible to prove that two mutable borrow do not intersect.
RefCellRefcell let you turn an immutable borrow to a mutable one:
fn f(v: &RefCell<Vec<i32>>) { // Notice that v is an immutable borrow.
let &mut vec = v.get_mut(); // This will compile.
vec.push(0);
}
Some important functions for RefCell<T>:
borrow: turn a &RefCell<T> into &Tborrow_mut: turn a &RefCell<T> into &mut T.Notice that you must ensure that either there is only one mutable borrow or there are only immutable borrows at any time. RefCell track the number of borrows, so if the property is violated, it will panic.
CellCell<T> is a special version mainly used for type T that is Copy.
get, set: Instead of calling borrow or borrow_mut, you can direct use x.get() or x.set(val) to get/set the value.PinPin<T> is a type that guaranteed the underlying object cannot be moved, where T is a pointer type (&mut, Box, etc.). We need this guarantee to ensure memory safety because a future can have self-references to itself, which make it unsafe to be moved.
Some useful functions:
set: Overwrite the underlying data.as_ref, as_mut: If you have a pin = &mut Pin(ptr), then pin.as_mut() gives you Pin(&mut *ptr). If ptr is a mutable reference &mut v, then Pin(&mut *ptr) = Pin(&mut v), which is essentially making a copy of Pin(&mut v). This is useful when some operations consumes Pin.Other useful functions:
Box::pin: Create a Pin<Box<T>>. Notice that once Pin is created, you cannot move out T anymore.Option::as_pin_mut: If pin = Pin(&mut Some(x)), then pin.as_pin_mut() = Some(Pin(&mut x)). If pin = Pin(&mut None), then pin.as_pin_mut() = None. It projects the pin to the underlying value.pin-projectOne key operation we need is the ability to project a Pin into fields:
struct Wrap<F: Future> {
future: F,
}
fn f(pin: Pin<&mut Wrap>) {
// We need pin.future to be Pin<&mut F> in order to poll the future.
}
To get rid of the need of writing unsafe code ourselves, we use an external library called pin-project. We mark the type using #[pin_project] and its fields as #[pin], and the library will generate extra codes for use (you can imagine that pin_project is a complicated macro). Then, we can use project() to project pin onto the fields:
#[pin-project]
struct Wrap<F: Future> {
#[pin] future: F,
unpin: usize,
}
fn f(pin: Pin<&mut Wrap>) {
let this = pin.project();
// You can imagine this is
// WrapPinned {
// future: Pin<&mut F>,
// unpin: &mut usize,
// }
this.future.poll();
}
#[pin-project] or #[pin]. We have done that for you.pin.project().x).
x of type T is marked #[pin], then pin.project().x is a Pin<&mut T> pointing to x.x is not marked #[pin] and x must be of type T such that T: Unpin (i.e., T is safe to move). In this case, pin.project().x will be a normal mutable reference &mut T.project() consumes pin. If you want to do projection more than once, use Pin::as_mut as described above.Trait is how you do polymorphism in rust. Rust does not have other construct such as inheritance.
You define a trait like:
trait MyTrait {
fn implemented(&self) -> u32;
fn provided(&self) -> String {
implemented(self).to_string()
}
}
When other people implement MyTrait for their type T, they will need to implement the function named implemented, and they get a default implementation of provided for free (they can also override provided if they want.). The following is an example of implementing MyTrait for Vec<i32>
impl MyTrait for Vec<i32> {
fn implemented(&self) -> u32 {
self.len() as u32
}
}
This is similar to an interface in Java:
public interface MyTrait {
int implemented();
default String provided() {
return implemented().to_string()
}
}
public class IntVec implements MyTrait {
public int implemented() {
return len();
}
}
SelfIn the definition of a trait, Self refers to the type that is implementing this trait:
trait MyTrait {
fn f(&self) -> Self;
}
impl MyTrait for Vec<i32> {
fn f(&self) -> Self {
self.clone()
}
}
An associated type connects a type placeholder with a trait. The implementer must decide the type:
trait MyTrait {
type Output;
fn get(&self) -> Self::Output;
}
impl MyTrait for Vec<i32> {
type Output = i32;
fn get(&self) -> Self::Output {
self[0]
}
}
You can put trait bounds on type variables:
fn f<T: MyTrait>(t: T) {
// Can be called only when T implements MyTrait.
}
fn g<T>(t: T)
where
T: MyTrait
{
// The same as above.
}
struct U<T>(T)
where
T: MyTrait + OtherTrait, // Use + to indicate intersection.
T::Output: Clone; // You can also put restrictions on associated types.
DropThe Drop trait is defined as
trait Drop {
fn drop(&mut self);
}
drop will be called when the variable goes out of scope, which is equivalent to destructors in C++. You can implement this trait to customize behaviors when dropped.
Clone, CopyClone represents types that can be cloned. Copy represents types that can be trivially copied, which usually means a memcpy operation. Most primitive types (e.g., u32, bool, etc.) implements Copy.
Deref, DerefMutDeref and DerefMut represent types that can be dereference by the operator *. For example, Rc<T> implements Deref<T>, so that if rc: Rc<T>, *rcwill be T. The only difference between Deref and DerefMut is you get an immutable or mutable ones.
When you are calling a function, for example, rc.method(), rust will try to find method in Rc first, if not found, it will try to dereference into inner T and find the method, and so on.
FnOnce, FnMutIn rust, FnOnce represents function types that can be called once, and FnMut represents function that can be called multiple times.
UnpinUnpin are types that can be moved safely.
PhantomDataWhen defining a generic type, (e.g., T<A, B>), rust requires that all the type variables are used at least once. Thus, you will see we placed PhantomData inside structs. This type does not take any memory space and creates no overhead.
What is the recommended development environment on Myth?
rust-analyzer here.rust plugin for Visual Studio Code here.cargo doc --document-private-items on the project root.cd target/doc && python3 -m http.server [port] to open an HTTP server for serving docs.You can replace Visual Studio Code with any IDE you like, such as vim, emacs, … The key is to get language client support, for example, vim-lsp, etc.
When installing Rust on Myth, it shows:
warning: If you are sure that you want both rustup and your already installed Rust warning: then please reply y
What should I do?
Just reply y.
I am getting rustup: command not found.
Remember to source your ~/.bashrc or re-login.
Context?WakerInner) that implements ArcWake. Then, you call create_waker to get a Waker type. finally, you call Context::from_wakerto create a Context. The whole thing look something like:let waker_inner = WakerInner { ... };
let waker = create_waker(waker_inner);
let mut context = Context::from_waker(&waker);
executor::executor::itest_executor_do_not_poll_suspended
executor::io::itest_executor_io_accept_no_busy_wait
executor::io::itest_executor_io_read_no_busy_wait
executor::io::itest_executor_io_write_no_busy_wait
executor::queue::itest_queue_pop_no_busy_wait
executor::queue::itest_queue_push_no_busy_wait
executor::timer::itest_edge_only_most_recent_waker_should_wake
executor::timer::itest_executor_timer_no_busy_wait
Also, depending on your implementation, some queue tests might fail.ArcWake so that your waker will put the future back to the executor queue when wake_by_ref is called.Pending, they also register the waker (you get that from Context). For example, you will need to call register_fd_events in AsyncRead, etc.error[E0308]: mismatched types
= note: expected struct `Rc<RefCell<Pin<Box<(dyn Future<Output = ()> + 'static)>>>>`
found struct `Rc<RefCell<Pin<Box<F>>>>`
Rust somehow is not able to deduce the type correctly if you let a variable to be
let future = Rc::new(RefCell::new(Box::pin(future)));
put it into the queue directly:
self.queue.borrow_mut().push_back(Rc::new(RefCell::new(Box::pin(future))));
WakerInner?Option::take.Handle does not implement Clone?Clone for Handle, but not required.Waker keep holding the future even if the future has completed?Rc<RefCell<Option<BoxFuture>>>, or you need to destroy all wakers once the future completes. We simplify this part and will not test it.Pending?WouldBlock error from the underlying IO operations.AsyncAccept, how can I access the TcpListener?self.listener.0.AsyncAccept, how can I turn a TcpStream into an AsyncStream?AsyncStream::from_tcp_stream. Do not construct the stream by yourself or else you might forget to set it to non-blocking mode.bind and connect asynchronous?read_exact and write_all, just use the read and write async functions you just implemented. You will do something likepub async fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> io::Result<()> {
// ...
let num_bytes_read = self.read(&mut buf[bytes_read..]).await?;
// ...
}
read writes the data to the buffer in-place.TcpStream::read (and so does your AsyncStream::read) will return 0 (zero byte is read).register_fd_events?&TcpStream or &TcpListener as object, and you will need to specify whether it is a read or write interest. You also need to pass a clone of the waker into it.Token returned by register_fd_events. If you drop the token,read and accept, it should Read. For write it should be Write.wait_fd_events to let epoll return ready events.register_fd_events to prevent it being drop. When the token is dropped, it will do clean-up and deregister the event.std::collections::BinaryHeap.Token in our design?Token is there to ensure the events are cleared after a future is dropped. This ensures that file descriptors are released if the future is dropped before receiving the IO event. We won’t be so strict on checking if you have done the clean-up.wait_fd_events, check if any timer expiressend, recv, …) async. That is,pub async fn send(self, val: T) -> io::Result<Channel</* ... */>>
pub async fn recv(self) -> (io::Result<Channel</* ... */>>, T)
send, choose_left, and choose_right?choose_left should send a single byte 0 and choose_right should send a byte 1.Channel {
stream, // equivalent to `stream: stream`,
phantom: PhantomData
}
step in Channel<Var<N>, Env> do?step. You will need to restore the type and the Env just after step from the corresponding Rec type is called. For example, If Env = (S0, (S1, (S2, ()))) and we have Var<Succ<Zero>>, then you need to retrieve S1 and restore Env = (S1, (S2, ())). You should see now how you will use Pop<N>. Notice that you will not need separate cases for Var<Zero> and Var<Succ<Zero>>.SessionType is not implemented for Pop<N>::Headimpl<...> Channel<...>
where
<Env as Pop<N>>::Head: SessionType
Env and Env::List)?SessionType, but all the correct usage still need to compile.Recv, how do we create a buffer with dynamic length len?let buf = vec![0; len].