Sửa lỗi Rust E0728: Sử dụng '.await' bên ngoài ngữ cảnh Async

beginner🦀 Rust2026-07-14| Rust 1.39+ (Stable), mọi hệ điều hành (Linux, macOS, Windows)

Error Message

error[E0728]: 'await' is only allowed inside 'async' functions and blocks
#rust#async#tokio#backend

Vấn đềCó lẽ bạn đang xây dựng một dịch vụ—chẳng hạn như lấy dữ liệu JSON bằng reqwest hoặc truy vấn cơ sở dữ liệu với sqlx. Bạn thêm .await vào một lệnh gọi hàm, hy vọng nó hoạt động, nhưng trình biên dịch lại báo lỗi:

error[E0728]: 'await' is only allowed inside 'async' functions and blocks
  --> src/main.rs:10:5
   |
9  | fn main() {
   |    ---- đây không phải là 'async'
10 |     let response = my_async_function().await;
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^ chỉ được phép bên trong các hàm và khối 'async'

Nguyên nhân gốc rễMô hình bất đồng bộ (async) của Rust rất đặc biệt. Không giống như Node.js hay Python, nơi các tác vụ có thể tự động chạy ngầm, các future trong Rust mang tính "lười biếng" (lazy). Chúng nằm yên và không làm gì cả cho đến khi thực sự được poll (thăm dò).

Khi bạn gọi .await, bạn đang báo hiệu cho trình biên dịch chuyển đổi hàm của mình thành một state machine (máy trạng thái) có thể tạm dừng và tiếp tục thực thi. Phép thuật chuyển đổi trạng thái này chỉ hoạt động bên trong một ngữ cảnh được thiết kế cho nó. Các hàm đồng bộ thông thường không biết cách tạm dừng. Nếu bạn cố gắng sử dụng .await bên trong chúng, trình biên dịch sẽ đi vào ngõ cụt và báo lỗi E0728.

Cách khắc phục lỗi### 1. Đánh dấu hàm là asyncCách sửa nhanh nhất thường là cách đúng nhất. Chỉ cần thêm async vào trước định nghĩa hàm của bạn. Điều này thông báo cho Rust rằng hàm này không trả về giá trị ngay lập tức; thay vào đó, nó trả về một Future mà cuối cùng sẽ cho ra kết quả.

Trước khi sửa:

fn fetch_data() {
    let data = some_async_call().await; // Lỗi E0728
}

Sau khi sửa:

async fn fetch_data() {
    let data = some_async_call().await; // Biên dịch thành công
}

2. Khởi tạo một Async Runtime cho main()Các lập trình viên mới thường gặp phải rào cản này trong fn main(). Theo mặc định, điểm bắt đầu của Rust là đồng bộ và không thể await. Để khắc phục, bạn cần một executor để quản lý các tác vụ của mình. Tokio là tiêu chuẩn công nghiệp, được sử dụng bởi hơn 80% các dự án Rust async.

Đầu tiên, thêm phiên bản 1.x của tokio vào tệp Cargo.toml của bạn:

[dependencies]
tokio = { version = "1", features = ["full"] }

Sau đó, sử dụng macro #[tokio::main] để chuyển đổi hàm main của bạn:

#[tokio::main]
async fn main() {
    let result = my_async_task().await;
    println!("Tác vụ hoàn tất: {:?}", result);
}

3. Kết nối Async và Sync bằng block_onCuối cùng bạn sẽ gặp trường hợp mà async không phải là một lựa chọn. Có thể bạn đang triển khai một trait đồng bộ như std::fmt::Display hoặc tích hợp với một codebase cũ. Trong những vùng "chỉ đồng bộ" này, hãy sử dụng block_on để buộc luồng hiện tại chờ đợi một future.

Sử dụng futures::executor::block_on:

use futures::executor::block_on;

fn sync_function() {
    // Lệnh này chặn luồng cho đến khi future được giải quyết
    let data = block_on(my_async_task());
    println!("Đã nhận dữ liệu: {:?}", data);
}

4. Sử dụng Async BlockNếu bạn chỉ cần một phần nhỏ logic bất đồng bộ bên trong một phạm vi đồng bộ lớn hơn, hãy sử dụng một khối async. Chỉ cần nhớ rằng bản thân khối đó là một Future. Nó sẽ không thực thi cho đến khi bạn chuyển nó vào một runtime.

fn main() {
    let my_future = async {
        do_something().await;
    };
    
    // Mã ở trên vẫn chưa chạy. Chúng ta phải thực thi nó:
    futures::executor::block_on(my_future);
}

Xác minhChạy cargo check để xác minh bản sửa lỗi. Nếu E0728 biến mất, cú pháp của bạn đã ổn. Tuy nhiên, hãy cẩn thận với những cạm bẫy runtime sau:

  • Lồng runtime (Runtime Nesting): Không bao giờ gọi block_on bên trong một hàm async hiện có. Trong các runtime như Tokio, điều này sẽ gây ra lỗi panic ngay lập tức.- Deadlocks: Đảm bảo các tác vụ async của bạn thực sự có cách để kết thúc, nếu không block_on sẽ treo toàn bộ luồng của bạn mãi mãi.## Mẹo phòng ngừa- Bắt đầu với Async: Nếu ứng dụng của bạn có tương tác với mạng hoặc ổ đĩa, hãy sử dụng #[tokio::main] ngay từ đầu. "Sự lan tỏa async" (Async creep) là có thật—việc chuyển đổi một codebase đồng bộ sang bất đồng bộ sau này sẽ khó khăn hơn nhiều.- Lan truyền Async lên trên: Cố gắng giữ cho ngăn xếp lệnh gọi (call stack) của bạn là async xuyên suốt lên đến cấp cao nhất. Chỉ sử dụng block_on ở cấp cao nhất hoặc tại các ranh giới nghiêm ngặt nơi async không khả thi.- Kiểm tra hỗ trợ Trait: Các trait tiêu chuẩn như Iterator chưa hỗ trợ async. Nếu bạn cần, hãy sử dụng crate async-trait hoặc Stream từ crate futures.

Related Error Notes