Rust Cheatsheet

Written — Updated
  • Asynchronous Rust
    • Asynchronous Closures
      • You can define code blocks as async, just like you can with functions. You'll usually need to use move on the code block as well.
      • the_function(|| move async {
          // the code
        }).await?;
        
    • Taking Asynchronous Closure Arguments
      • It starts like usual, with an Fn, FnOnce, or FnMut trait boundary, but you have to define the return type and you can't do impl Future in the trait.
      • The way around this is to define another type parameter, and put the trait boundary on that.
      • async fn takes_an_async_closure<F, Fut, R, E>(f : F) -> impl Future
        where
            F: FnOnce() -> Fut,
            Fut: Future<Output = Result<R, E>>,
            R: Send,
            E: Send
        {
          f().await
        }
        
    • Dealing with Time
      • Periodic Functions
        • For loops that need to run periodically, normally you would use sleep to wait for the next call.
        • But Tokio provides an interval object which accounts for the time that the call actually took. So your 500ms periodic poll will actually run every 500ms, instead of 500ms + some random amount.
        • The first call to interval.tick() will trigger right away. You can use interval_at instead if this is undesirable, but generally it just means you move the wait part of the loop to the top.
      • Timeouts
        • tokio::time::timeout(duration, operation()) is an easier way to do a timeout than using select! with a sleep.
  • Inner Arc Pattern
    • For types that you want to always be clonable and within an Arc without needing to deal with the Arc everywhere, you can make an outer NewType that just contains an Arc<InnerType>.
    • In this case you do have to implement Clone manually for things to really work properly. But it's pretty easy.
    • struct Queue(Arc<QueueInner>);
               
      impl Clone for Queue {
        fn clone(&self) -> Queue {
            Queue(self.0.clone())
        }
      }
      
      // Implement methods on the outer type, which access the inner type.
      impl Queue {
        fn do_job(&self) {
      // do stuff with self.0 here...
        }
      }
      
      
    • This pattern should only be used for types that you know will be passed around between threads or async contexts, such as connection pools. Otherwise it's usually better to let the user of your library add an Arc if needed.
  • Partial Moves
    • Rust doesn't allow moving part of a structure out, but there are better ways than just clone.
    • Use mem::replace to take items out from structures without needing to take ownership of the entire thing.
    • Destructuring also helps when you want to eventually move all the items out of the structure.
      • Lately I've been just destructuring more and more.
  • Serde
    • To enable auto-derive of Serialize and Deserialize traits, the best way is to enable the derive feature in the serde crate. You can also just include the serde-derive crate manually.
  • Performance
    • fxhash crate is good for fast hashing that doesn't need to be cryptographically secure.
    • Rust Performance Book
    • Enable Link-time Optimization
      • [profile.release]
        lto = true
        
  • Tracing Level Format
    • tracing_subscriber::EnvFilter takes the format target[span{field=value}]=level
    • All except for level are optional.
    • value, if present, acts as a regex unless it's a number or boolean. As a string, it should be surrounded by quotes. If absent, then it just filters on the presence of field.
    • If exporting to GRPC with HTTPS, you need to make sure that the tonic crate has the tls-roots feature enabled. Otherwise you'll get a HTTP/2 "frame with invalid size" error, which is not very helpful for figuring out the issue.

Thanks for reading! If you have any questions or comments, please send me a note on Twitter.