惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
O
OpenAI News
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
罗磊的独立博客
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
雷峰网
雷峰网
V
Visual Studio Blog
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
博客园 - 【当耐特】
G
Google Developers Blog
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
AI
AI
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

oida.dev | TypeScript, Rust

TypeScript's `erasableSyntaxOnly` Flag Unsafe for work Tokio: Channels Tokio: Getting Started Network Applications on the Tokio Stack Remake, Remodel, Reduce. The `never` type and error handling in TypeScript 5 Inconvenient Truths about TypeScript Refactoring in Rust: Introducing Traits Refactoring in Rust: Abstraction with the Newtype Pattern Announcing the TypeScript Cookbook TypeScript: Iterating over objects The road to universal JavaScript 10 years of oida.dev Rust: Tiny little traits The TypeScript converging point How not to learn TypeScript Getting started with Rust Introducing Slides and Coverage TypeScript: The humble function overload TypeScript + React: Children types are broken TypeScript: In defense of any Rust: Enums to wrap multiple errors Dissecting Deno Error handling in Rust TypeScript: Unexpected intersections Upgrading Node.js dependencies after a yarn audit TypeScript: Array.includes on narrow types TypeScript + React: Typing Generic forwardRefs shared, util, core: Schroedinger's module names Learning Rust and Go TypeScript: Narrow types in catch clauses TypeScript: Low maintenance types Tidy TypeScript: Name your generics Tidy TypeScript: Avoid traditional OOP patterns Tidy TypeScript: Prefer type aliases over interfaces Tidy TypeScript: Prefer union types over enums My new book: TypeScript in 50 Lessons Go Preact! ❤️ this in JavaScript and TypeScript TypeScript and ECMAScript Modules TypeScript + React: Why I don't use React.FC TypeScript + React: Component patterns TypeScript: Augmenting global and lib.dom.d.ts Vite with Preact and TypeScript TypeScript: Union to intersection type 11ty: Generate Twitter cards automatically Are large node module dependencies an issue? TypeScript: Variadic Tuple Types Preview TypeScript: Improving Object.keys Remake, Remodel. Part 4. TypeScript + React: Typing custom hooks with tuple types TypeScript: Assertion signatures and Object.defineProperty TypeScript: Check for object properties and narrow down type Boolean in JavaScript and TypeScript void in JavaScript and TypeScript Symbols in JavaScript and TypeScript Why I use TypeScript TypeScript + React: Extending JSX Elements TypeScript: Validate mapped types and const context TypeScript: Match the exact object shape TypeScript: The constructor interface pattern Streaming your Meetup - Part 4: Directing and Streaming with OBS Streaming your Meetup - Part 3: Speaker audio Streaming your Meetup - Part 2: Speaker video Streaming your Meetup - Part 1: Basics and Projector TypeScript and React Guide: Added a new styles chapter TypeScript and React Guide: Added a new render props chapter TypeScript and React: Styles and CSS TypeScript and React TypeScript and React Guide: Added a new prop types chapter TypeScript without TypeScript -- JSDoc superpowers TypeScript: Mapped types for type maps JAMStack vs serverless web apps The Unsung Benefits of JAMStack Sites TypeScript: Ambient modules for Webpack loaders My most favourite talks in 2018 TypeScript and React Guide: Added a new context chapter TypeScript: Built-in generic types TypeScript: Type predicates JSX is syntactic sugar TypeScript and React Guide: Added a new hooks chapter Getting your CfP application right FAQ on our Angular Connect Talk: Automating UI development TypeScript and Substitutability Debugging Node.js apps in TypeScript with Visual Studio Code From Medium: Deconfusing Pre- and Post-processing From Medium: PostCSS misconceptions Saving and scraping a website with Puppeteer Cutting the mustard - 2018 edition Wordpress as CMS for your JAMStack sites My most favourite podcast episodes in 2017 My most favourite talks in 2017 My most favourite books in 2017 The Best Request Is No Request, Revisited Not so hidden figures - Organizing ScriptConf My podcast journey to ScriptCast Grid layout, grid layout everywhere! #scriptconf and #devone Object streams in Node.js
Tokio: Macros
2024-11-20 · via oida.dev | TypeScript, Rust

join #

tokio::join! lets you run multiple futures concurrently, and returns the output of all of them. For JavaScript developers: Promise.all is a good equivalent.

Here’s an example where I wait for multiple URLs to finish.

let (resp_1, resp_2) = tokio::join!(
reqwest::get("https://oida.dev"),
reqwest::get("https://oida.dev/slides")
);

There’s one caveat tough that you need to be aware of: Those futures run on the same thread concurrently, but not in parallel. If you want to run them in parallel, use tokio::spawn to spawn a new task for each future. tokio::spawn returns a JoinHandle, which implements also Future, meaning that you can join on it.

async fn fetch(url: &str) -> reqwest::Result<String> {
reqwest::get(url).await?.text().await
}

let (body_1, body_2) = tokio::join!(
task::spawn(fetch("https://oida.dev")),
task::spawn(fetch("https://oida.dev/slides"))
);

There is also try_join, which returns a Result instead of a tuple. If one of the futures fails, the whole try_join fails.

select #

You’re going to love/hate the select macro. First and foremost, it’s really cool and handy for a plethora of situations, but its syntax can be confusing, and your Rust formatter won’t be happy with it. That aside, it’s really helpful and sometimes also necessary.

My feeling is that it has been inspired by how Go’s select statement works when working with go-routines and reading from channels, but I might be wrong.

Anyway, the select macro allows you to run multiple futures concurrently, wait on their result, and then act on the first one that completes. If you know JavaScript, it’s very similar to Promise.race.

The syntax can be a bit weird. There are no .await calls and no let assignments. The snytax to write one of the select branches is pattern = future/async expression => handler. Think of transforming an if let call like this:

if let Ok(resp) = reqwest::get("https://oida.dev").await {
// tbd
}

into this:

Ok(resp) = reqwest::get("https://oida.dev") => {
// tbd
}

Let’s look at an example where I fetch data from my website and try to finish the request in less than 250 milliseconds.

use std::time::Duration;

use tokio::time;

#[tokio::main]
async fn main() {
// (1)
tokio::select! {
// (2)
Ok(resp) = reqwest::get("https://oida.dev") => {
println!("{:?}", resp.text().await.unwrap());
}
// (3)
_ = time::sleep(Duration::from_millis(250)) => {
println!("Timeout");
}
// (4)
else => {
println!("Something else happened");
}
}
}

Here’s whats going on.

  1. This is the macro. It is followed by a block with the futures you race against each other.
  2. The first future is returned from the reqwest::get call. We make a call to my website. If this future finishes first, we print the response.
  3. The second future is a time::sleep call from the tokio crate. If this future finishes first, we print “Timeout”.
  4. Since we destructured the Ok variant of the first future, we don’t handle the case where the reqwest::get call errors. For that, we can use the else block that takes care of all other cases.

This is tokio::select in a nutshell and the basic syntax. There are more nuances to it that are well explained in the official doc, but from my experience, you will be good with what you see above.

tokio::select is a good alternative if you need to race multiple futures against each other and spawning tasks / waiting for their result is too much. You will see in the exercise below that it can greatly reduce code complexity.

pin #

Explaining the pin macro without explaining what pinning means in the context of async Rust won’t work. I try to explain it very briefly: Before a future starts executing, we need to be able to move it around in memory. This is especially true when using Tokio, since a future might be executed on a different worker thread than the one it came from.

Once a future startse executing, it must not be moved around in memory anymore. This is where pinning comes into play. It tells the compiler that everything it needs to execute the future is in place and that it can’t be moved around anymore. If you want to know more about it, please check out Adam Chalmer’s excellent article on that topic.

The pin! macro allows you to pin a future on the stack. You will see Box::pin more often in the wild, pin! is an alternative to that.

Exercise 3: Rewrite the Chat Server with Macros #

Let’s refactor our chat server to work properly with Tokio’s select! macro:

  • Extract the loop into its own function
  • Use proper error handling instead of unwrap()
  • Let both sides of the chat run concurrently in a select block

Solution #

Please find the solution below. To make the code even terser, I renamed the buf variable to content, the addr will be passed as id. I will only focus on the run method, you can spawn run as a task:

tokio::spawn(run(socket, addr, tx, rx));

And here’s the solution:

// (1)
async fn run(
socket: TcpStream,
id: SocketAddr,
tx: broadcast::Sender<Message>,
mut rx: broadcast::Receiver<Message>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let (reader, mut writer) = socket.into_split();
let mut reader = BufReader::new(reader);

loop {
let mut content = String::new();
tokio::select! {
// (2)
Ok(msg) = rx.recv() => {
if msg.id != id {
writer.write_all(msg.content.as_bytes()).await?;
}
}
// (3)
Ok(_b_read) = reader.read_line(&mut content) => {
if content.trim() == "quit" {
break Ok(());
}
tx.send(Message { content, id })?;
}
}
}
}

  1. We extract the main loop into a separate function. The function takes the socket, the socket’s address as ID, the broadcast sender, and the brodcast receiver. The function returns a Result with an error type that implements Error, Send, and Sync. We want to bubble up errors to the caller, and since we run in an async context, the errors itself need to be thread-safe.
  2. In the select block we run two futures concurrently. The first future listens for incoming messages from the broadcast receiver. If a message is received, we check if the message is not from the current client and write the message to the writer.
  3. The second future listens for incoming messages from the client. If a message is received, we check if the message is “quit”. If it is, we break the loop and return Ok(()). Otherwise, we send the message to the broadcast sender.

And that’s it!

Tokio: Table of contents

  1. Getting Started
  2. Channels
  3. Macros

Related Articles