การดู : 124

08/11/2026 10:00น.

ปกบทความ Rust 2024 Edition ในปี 2026 มีกล่องสีส้มสองก้อนและรูปเฟืองบนพื้นน้ำเงินเข้ม พร้อมชิป Rust 1.85, let chains และ resolver 3

Rust 2024 Edition ในปี 2026: เปลี่ยนอะไรบ้าง ย้ายโปรเจกต์อย่างไร และได้ let chains เป็นของแถม

#Rust

#Rust Programming

#Rust Language

#Rust 2024 Edition

#cargo fix

#Rust 1.85

ลองนึกภาพว่าคุณกลับไปเปิดโปรเจกต์ Rust ที่เขียนค้างไว้เมื่อปีก่อน แล้วเห็นบรรทัด edition = "2021" ใน Cargo.toml ขณะที่ในข่าวและเอกสารของภาษา ทุกคนพูดถึง 2024 กันหมดแล้ว คำถามแรกที่ตามมามักเป็น ถ้าไม่ย้ายจะเกิดอะไรขึ้น และถ้าย้ายโค้ดจะพังตรงไหนบ้าง

คำตอบสั้นๆ คือ โปรเจกต์ที่ยังใช้ edition เดิมไม่ได้เสียหายอะไร และการย้ายส่วนใหญ่มีเครื่องมือช่วย แต่มีไม่กี่จุดที่เครื่องมือช่วยไม่ได้ และไม่กี่จุดที่เปลี่ยนพฤติกรรมของโค้ดอย่างเงียบๆ จึงควรรู้ล่วงหน้า วันนี้ Superdev Academy จะพาไปดูว่า Rust 2024 Edition เปลี่ยนอะไรที่กระทบโค้ดจริง ย้ายโปรเจกต์อย่างไร และอะไรที่ต้องตรวจเองเสมอ โดยอ้างอิงจากประกาศ Rust 1.85.0และEdition Guide ของ Rust 2024เป็นหลัก

เราตรวจข้อมูลกับเอกสารทางการอีกรอบเมื่อต้นเดือนพฤศจิกายน 2026 ตอนนั้น Rust 2024 ออกมาแล้วราว 20 เดือน และ stable ล่าสุดที่เราพบในบล็อกทางการคือ Rust 1.99.0 (1 ตุลาคม 2026) พร้อม 1.99.1 ที่ตามมาวันที่ 15 ตุลาคม 2026 รายการเปลี่ยนแปลงของ edition ไม่ได้ต่างจากตอนปล่อย แต่บริบทต่างไป เพราะ 2024 กลายเป็น edition ปัจจุบันแล้ว และมีฟีเจอร์ที่เพิ่งออกหลัง 1.85 ซึ่งเปิดให้ใช้เฉพาะ edition นี้ เราจึงเพิ่มหัวข้อ let chains ไว้ด้านล่าง

Edition คืออะไร และ 2024 มาพร้อมเวอร์ชันไหน?

Edition คือกลไกที่ Rust ใช้ปล่อยการเปลี่ยนแปลงที่ไม่เข้ากันกับโค้ดเก่า (backward-incompatible) โดยไม่บังคับให้ทุกคนต้องแก้ตามทันที แต่ละ crate เลือก edition ของตัวเองผ่านคีย์ edition ใน Cargo.toml ส่วนถ้าไม่เขียนคีย์นี้เลย Cargo จะถือว่าเป็น Rust 2015

Rust 2024 ถูกประกาศว่า stable พร้อมกับ Rust 1.85.0 เมื่อวันที่ 20 กุมภาพันธ์ 2025 ประกาศนั้นเรียกมันว่า edition ที่ใหญ่ที่สุดเท่าที่เคยปล่อยมา และบอกให้อัปเดตด้วยคำสั่ง rustup update stable ถ้าคุณเคยอ่านเรื่องที่มาของภาษานี้ในประวัติของ Rustจะเห็นว่าการเปลี่ยนแบบเป็นขั้นเป็นตอนผ่าน edition คือวิธีที่ชุมชนรักษาเสถียรภาพไว้ควบคู่กับการพัฒนาภาษา

ภาพรวมการเปลี่ยนแปลงใน Rust 2024

ประกาศ 1.85.0 แบ่งรายการเปลี่ยนแปลงตามส่วนของระบบ ฝั่งภาษา (language) มี RPIT lifetime capture rules, if let temporary scope, tail expression temporary scope, match ergonomics reservations, unsafe extern blocks, unsafe attributes, คำเตือน unsafe_op_in_unsafe_fn, การห้ามอ้างอิงถึง static mut, การเปลี่ยน never type fallback, การปรับ macro fragment specifiers, keyword gen และ reserved syntax

ฝั่ง standard library มี prelude ที่เพิ่ม Future กับ IntoFuture, IntoIterator สำหรับ Box<[T]> และฟังก์ชันที่กลายเป็น unsafe ได้แก่ std::env::set_var, std::env::remove_var และ CommandExt::before_exec ฝั่ง Cargo มี resolver ที่รู้จัก Rust version, การปรับชื่อ table และ key ให้สอดคล้องกัน และการปฏิเสธ default-features ที่ inherit มาแต่ไม่ได้ใช้ นอกจากนี้ยังมีการเปลี่ยนใน rustdoc และ rustfmt อีกส่วนหนึ่ง

รายการยาวขนาดนี้ไม่ได้แปลว่าโค้ดของคุณจะพังหมด ส่วนใหญ่กระทบเฉพาะโค้ดที่เขียนในรูปแบบเฉพาะ ด้านล่างเราคัดมาเฉพาะข้อที่ผู้พัฒนาทั่วไปมีโอกาสเจอมากที่สุด

1. impl Trait ใน return position จับ lifetime มากขึ้น

เรื่องนี้เป็นหนึ่งในการเปลี่ยนที่ทำให้ borrow checker แสดง error ใหม่ในโค้ดเดิมได้ ใน Rust 2024 เมื่อฟังก์ชันคืนค่าเป็น impl Trait (เรียกว่า RPIT) และไม่ได้เขียน use<..> กำกับ ค่านั้นจะ capture generic parameter ทุกตัวที่อยู่ใน scope รวมถึง lifetime ด้วย ส่วนใน edition ก่อนหน้าจะ capture lifetime โดยปริยายเฉพาะบางตำแหน่ง Edition Guide เขียนเทียบไว้ดังนี้

fn f_implicit(_: &()) -> impl Sized {}
// In Rust 2021 and earlier, the above is equivalent to:
fn f_2021(_: &()) -> impl Sized + use<> {}
// In Rust 2024 and later, it's equivalent to:
fn f_2024(_: &()) -> impl Sized + use<'_> {}

ผลที่ตามมาคือค่าที่คืนออกไปอาจผูกอายุไว้กับ argument มากกว่าที่เคย ทำให้โค้ดฝั่งผู้เรียกที่เคยผ่านอาจถูก borrow checker ปฏิเสธ ถ้าต้องการบอกให้ชัดว่าค่าที่คืนไม่ได้ยึด lifetime ใดเลย ใช้ use<> ได้ และ use<..> เป็นฟีเจอร์ที่เสถียรตั้งแต่ Rust 1.82 จึงใช้ได้ในทุก edition

fn capture<'a>(_: &'a ()) -> impl Sized + use<> {}

fn test<'a>(x: &'a ()) -> impl Sized + 'static {
    capture(x) //~ OK
}

เครื่องมือที่ช่วยคือ lint impl_trait_overcaptures ในกลุ่ม rust-2024-compatibility และ cargo fix --edition ที่เติม use<..> ให้เองได้ในกรณีส่วนใหญ่ ข้อยกเว้นที่ Edition Guide ระบุไว้คือฟังก์ชันที่รับ argument แบบ impl Trait (APIT) เพราะพารามิเตอร์ชนิดนั้นไม่มีชื่อให้อ้างถึงใน use<..> ต้องตั้งชื่อ generic ให้มันเองก่อน ขั้นนี้ต้องทำด้วยมือ

2. if let ปล่อย temporary ก่อนเข้า else

การเปลี่ยนนี้ละเอียดอ่อนแต่ช่วยกันบั๊กที่หายากอย่าง deadlock ได้โดยตรง ใน Rust 2024 ค่า temporary ที่เกิดจากการประเมินนิพจน์ใน if let จะถูก drop ก่อนเข้าบล็อก else ส่วนใน edition เก่ามันถูกเก็บไว้จนจบ if let/else ทั้งก้อน ตัวอย่างจากคู่มือคือโค้ดที่ถือ read lock ของ RwLock ค้างไว้

use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("value is {x}");
    } else {
        let mut v = value.write().unwrap();
        if v.is_none() {
            *v = Some(true);
        }
    }
    // <--- Read lock is dropped here in 2021
}

ใน edition 2021 read lock จาก value.read() ยังถูกถือไว้ตอนเข้า else การขอ write lock ในบล็อกนั้นจึงเกิด deadlock ส่วนใน 2024 read lock ถูกปล่อยก่อนเข้า else โค้ดเดียวกันนี้จึงทำงานได้ถูกต้อง สิ่งที่ต้องระวังในแง่การย้ายคือ lint if_let_rescope จะเตือนจุดที่พฤติกรรมเปลี่ยน และ cargo fix --edition เลือกแก้โดยเขียน if let เป็น match ซึ่งรักษาอายุ temporary แบบ 2021 ไว้ คู่มือเตือนว่าในตัวอย่าง deadlock ข้างบน การแก้เป็น match จะยังถือ lock ค้างอยู่ในแขน _ เช่นเดิม จึงไม่แก้ปัญหา ควรไล่ดูผลการแก้ทีละจุดและย้อนกลับมาใช้ if let ถ้าพฤติกรรมแบบ 2024 คือสิ่งที่ต้องการ

3. อ้างอิงถึง static mut ถูกห้ามโดยค่าเริ่มต้น

lint static_mut_refs เปลี่ยนเป็นระดับ deny โดยปริยายใน 2024 คือห้ามสร้าง reference ทั้งแบบ shared และ mutable ไปยัง static mut เหตุผลที่คู่มืออธิบายคือ การสร้าง reference ที่ละเมิดกฎ mutability XOR aliasing เป็น undefined behavior ทันทีตั้งแต่ตอนสร้าง แม้ไม่เคยอ่านหรือเขียนผ่านมันเลย และการรับประกันกฎนี้กับ static mut ต้องคิดทั้งโปรแกรมพร้อมกัน ซึ่งยากมากเมื่อมี reentrancy หรือหลาย thread

static mut X: i32 = 23;
static mut Y: i32 = 24;

unsafe {
    let y = &X;             // ERROR: shared reference to mutable static
    let ref x = X;          // ERROR: shared reference to mutable static
    let (x, y) = (&X, &Y);  // ERROR: shared reference to mutable static
}

เรื่องนี้ไม่มีเครื่องมือแก้อัตโนมัติ ต้องเขียนใหม่เอง คู่มือแนะนำทางเลือกเรียงตามลำดับที่ควรพิจารณา เริ่มจากเลี่ยง global ถ้าเลี่ยงได้ ใช้ atomic สำหรับค่าง่ายๆ ใช้ Mutex หรือ RwLock สำหรับ type ที่ซับซ้อนกว่า ใช้ LazyLock หรือ OnceLock เมื่อต้องการ initialize ครั้งเดียวและทำเป็น const ไม่ได้ และถ้าจำเป็นต้องคง static mut ไว้จริง เช่นส่ง pointer ให้ไลบรารี C ให้ใช้ &raw mut หรือ &raw const แทนการสร้าง reference

use std::sync::atomic::Ordering;
use std::sync::atomic::AtomicU64;

static COUNTER: AtomicU64 = AtomicU64::new(0);

fn main() {
    COUNTER.fetch_add(1, Ordering::Relaxed);
}

4. extern block ต้องมี unsafe และฟังก์ชันที่กลายเป็น unsafe

ใน Rust 2024 บล็อก extern ต้องเขียนเป็น unsafe extern เสมอ เพราะคนเขียนบล็อกเป็นผู้รับผิดชอบว่า signature ถูกต้อง ถ้าผิดผลคือ undefined behavior (Rust 1.82 เปิดให้เขียน unsafe extern ได้ในทุก edition แล้ว) แต่ละรายการในบล็อกกำกับด้วย safe หรือ unsafe ได้ รายการที่ไม่ระบุจะถือเป็น unsafe

unsafe extern "C" {
    // sqrt (from libm) may be called with any `f64`
    pub safe fn sqrt(x: f64) -> f64;

    // strlen (from libc) requires a valid pointer,
    // so we mark it as being an unsafe fn
    pub unsafe fn strlen(p: *const std::ffi::c_char) -> usize;

    // this function doesn't say safe or unsafe, so it defaults to unsafe
    pub fn free(p: *mut core::ffi::c_void);

    pub safe static IMPORTANT_BYTES: [u8; 256];
}

lint missing_unsafe_on_extern เติมคำว่า unsafe ให้อัตโนมัติ แต่ตรวจไม่ได้ว่า signature ที่เขียนไว้ถูกจริงหรือไม่ จึงต้องรีวิวเอง ส่วนฟังก์ชันที่กลายเป็น unsafe ใน 2024 คือ std::env::set_var, std::env::remove_var และ CommandExt::before_exec สองตัวแรกมีปัญหาเพราะการแก้ environment ของโปรเซสอาจไม่ปลอดภัยในโปรแกรมหลาย thread บนบางแพลตฟอร์ม จึงห้ามเรียกขณะที่ thread อื่นอาจกำลังทำงานอยู่ เพื่อไม่ให้โค้ดเดิมพังทันที ฟังก์ชันเหล่านี้เป็น unsafe เฉพาะตั้งแต่ edition 2024

fn main() {
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::set_var("FOO", "123") };
}

เครื่องมือแก้ให้โดยห่อด้วย unsafe แต่คู่มือเน้นว่ามันไม่ได้ยืนยันว่าการใช้งานถูกต้อง คุณยังต้องตรวจเองว่าไม่มี thread อื่นรันอยู่ตอนเรียก ถ้ารับประกันไม่ได้ ควรคิดหาวิธีอื่นแทนการใช้ environment variable

5. Cargo ใช้ resolver แบบรู้ Rust version

เมื่อเขียน edition = "2024" Cargo จะถือว่า resolver = "3" ให้อัตโนมัติ ซึ่งเป็น resolver ที่พิจารณาค่า rust-version ของแพ็กเกจตอนเลือกเวอร์ชันของ dependency (ก่อนหน้านี้ตั้งแต่ Rust 1.84.0 ต้องเปิดเองผ่าน resolver.incompatible-rust-versions = "fallback" ใน .cargo/config.toml)

# Cargo.toml
[package]
name = "my-crate"
edition = "2024"        # implies resolver = "3"
rust-version = "1.85"   # the resolver considers this when choosing dependency versions

ข้อควรระวังจากคู่มือมีสองข้อ ข้อแรก resolver เป็นการตั้งค่าระดับ workspace และถูกอ่านเฉพาะแพ็กเกจระดับบนสุด ข้อสอง ถ้าใช้ virtual workspace ต้องเขียนฟิลด์ resolver = "3" ใต้ [workspace] เองถ้าต้องการใช้ตัวใหม่ และไม่มีเครื่องมือย้ายอัตโนมัติสำหรับเรื่องนี้ คู่มือแนะนำให้ทดสอบกับ dependency ล่าสุดใน CI เพื่อเจอบั๊กของ dependency ให้เร็วที่สุด

ของแถมหลังย้าย: let chains ใช้ได้เฉพาะ edition 2024

นี่คือเหตุผลเชิงปฏิบัติที่ในปี 2026 ยังควรย้ายมา 2024 ประกาศ Rust 1.88.0 (26 มิถุนายน 2025) นำ let chains เข้าสู่ stable ซึ่งให้ต่อ let หลายตัวด้วย && ในเงื่อนไขของ if และ while ได้ โดยไม่ต้องซ้อน if let หลายชั้น ประกาศระบุว่าฟีเจอร์นี้ใช้ได้เฉพาะ edition 2024 เพราะพึ่งพาการเปลี่ยนเรื่อง temporary scope ของ if let ที่เราอธิบายในข้อ 2 ตัวอย่างจากประกาศคือ

if let Channel::Stable(v) = release_info()
    && let Semver { major, minor, .. } = v
    && major == 1
    && minor == 88
{
    println!("`let_chains` was stabilized in this version");
}

สรุปคือถ้าอยากเขียนแบบนี้ ต้องตั้ง edition = "2024" และใช้ toolchain 1.88.0 ขึ้นไป สำหรับเรื่อง edition ถัดไป เราค้นแล้วไม่พบประกาศอย่างเป็นทางการ มีเพียงบทความความเห็นที่คาดเดาถึง 2027 ซึ่งเราไม่ถือเป็นข้อมูลยืนยัน จึงไม่ขอสรุปอะไรเกินกว่านี้

ของใหม่ที่มากับ 1.85.0 ซึ่งไม่ผูกกับ edition

ประกาศ 1.85.0 ยังพูดถึง async closure ซึ่งเขียนเป็น async || {} คืนค่าเป็น future และยืมค่าจากตัวแปรที่ capture ได้ พร้อมเพิ่ม trait AsyncFn, AsyncFnMut และ AsyncFnOnce เข้า prelude นอกจากนี้ยังมี attribute #[diagnostic::do_not_recommend] ที่ซ่อน trait implementation ที่ระบุจากข้อความแนะนำของคอมไพเลอร์ และการขยาย FromIterator กับ Extend ให้ใช้กับ tuple ขนาด 1 ถึง 12 ตัวได้ ส่วนรายละเอียดวิธีใช้ของแต่ละเรื่อง ทีมเราแนะนำให้อ่านต่อที่ประกาศต้นฉบับ เพราะเรื่องเหล่านี้ไม่ได้อยู่ในขอบเขตของการย้าย edition

ขั้นตอนย้ายโปรเจกต์จริง

ขั้นตอนที่ Edition Guide แนะนำมีห้าขั้น (คู่มือยกตัวอย่างจาก 2015 ไป 2018 แต่ขั้นตอนเหมือนกันทุก edition) เราปรับมาใช้กับ 2024 ดังนี้ เริ่มจากอัปเดต toolchain ด้วย rustup update stable ให้ได้ 1.85.0 ขึ้นไป (ในปี 2026 ก็คือ stable ล่าสุดได้เลย และถ้าจะใช้ let chains ต้องเป็น 1.88.0 ขึ้นไป) แล้วทำตามลำดับ

cargo update
cargo fix --edition

ขั้นแรก cargo update เพื่ออัปเดต dependency เพราะบาง crate โดยเฉพาะ proc-macro และ crate ที่สร้างโค้ดตอน build อาจมีปัญหากับ edition ใหม่ และ release ใหม่ของมันอาจแก้ไว้แล้ว คู่มือแนะนำให้รันเทสต์ต่อทันทีและ commit แยกถ้าใช้ git ขั้นสอง cargo fix --edition จะตรวจโค้ดและแก้เท่าที่แก้ได้ ส่วนที่แก้ไม่ได้จะมีคำเตือนแสดงในคอนโซลให้ไปแก้ด้วยมือ ขั้นสามคือแก้ Cargo.toml

[package]
name = "foo"
version = "0.1.0"
edition = "2024"

ขั้นสี่ build และ test ด้วย cargo build กับ cargo test ถ้ามี warning ใหม่ ให้รัน cargo fix อีกครั้ง (ไม่ต้องมี --edition) เพื่อใช้คำแนะนำของคอมไพเลอร์ ขั้นห้าซึ่งไม่บังคับคือ cargo fmt เพื่อจัดรูปแบบตามกฎของ edition ใหม่ ควร commit แยกจากการเปลี่ยนโค้ดจริง เพื่อให้ git blame ไม่ปนกัน

สิ่งที่เครื่องมืออัตโนมัติไม่ทำให้

ประกาศ 1.85.0 เตือนไว้ว่า cargo fix ทำงานแบบระวัง คือรักษาพฤติกรรมเดิมไว้ ผลที่ได้จึงไม่ใช่ข้อเสนอแนะที่เหมาะกับทุกโปรเจกต์ และคู่มือการย้ายระบุว่าการย้ายอัตโนมัติไม่อัปเดต doctest และโค้ดที่ใช้ macro หรือ build-time code generation อาจต้องแก้เองเพิ่ม ทีมเราสรุปเป็นเช็กลิสต์สั้นๆ ก่อน merge ได้ว่า ให้เปิดดู diff ของ if let ที่ถูกเปลี่ยนเป็น match ทุกจุด ตรวจ signature ใน unsafe extern ทุกบล็อก ตรวจทุกจุดที่เรียก set_var และ remove_var ว่าอยู่ในโค้ดที่ทำงาน thread เดียวจริง รัน doctest แยกต่างหาก และเทสต์ด้วย dependency ล่าสุดใน CI

ถ้าความรู้เรื่อง lifetime ของคุณยังไม่แน่นพอที่จะอ่าน error จาก RPIT ได้สบาย การกลับไปทบทวนกฎ Ownershipและการยืมข้อมูลแบบ Borrowingก่อนจะช่วยได้มาก ส่วนใครที่ยังตัดสินใจไม่ได้ว่าจะเริ่มเรียน Rust ดีไหม ลองอ่านทำไมต้อง Rustเป็นจุดเริ่มต้นครับ


สรุป: ย้าย edition ไม่ได้น่ากลัว แต่ห้ามกดผ่านโดยไม่อ่าน diff

Rust 2024 มาพร้อม 1.85.0 และเป็น edition ที่ใหญ่ที่สุดเท่าที่ทีม Rust เคยปล่อย แต่การเปลี่ยนที่กระทบโค้ดส่วนใหญ่ของนักพัฒนาทั่วไปมีไม่กี่เรื่อง คือ RPIT ที่จับ lifetime มากขึ้น, temporary ของ if let ที่ถูกปล่อยเร็วขึ้น, ข้อห้ามเรื่อง reference ไปยัง static mut, unsafe extern กับฟังก์ชันที่กลายเป็น unsafe และ resolver 3 ใน Cargo โดยมี let chains ตั้งแต่ 1.88.0 เป็นของแถมที่ใช้ได้เฉพาะ edition นี้ เครื่องมือ cargo fix --edition ช่วยได้เยอะ แต่เป็นเครื่องมือที่รักษาพฤติกรรมเดิมไว้ ไม่ได้ตัดสินแทนเราว่าโค้ดควรเป็นแบบไหน

ถ้าให้เก็บไปข้อเดียว ให้ถือว่าผลจาก cargo fix เป็นร่างแรกที่ต้องรีวิว ไม่ใช่คำตอบสุดท้าย โดยเฉพาะจุดที่เกี่ยวกับ lock, unsafe และ environment variable ที่ผิดพลาดแล้วไม่ได้ล้มทันที แต่ไปล้มทีหลังตอนโปรแกรมรันจริงครับ

🎯 ติดตามความรู้สาย Dev ได้ที่ Superdev Academy:

FAQ: คำถามที่พบบ่อยเกี่ยวกับบทความนี้

รวมคำถามและคำตอบที่ช่วยให้คุณเข้าใจเนื้อหาในบทความนี้ได้ดียิ่งขึ้น