ตัวแปรและความไม่แน่นอน
อย่างที่ได้กล่าวไว้ในหัวข้อ “การจัดเก็บค่าด้วยตัวแปร” ตัวแปรจะเป็น immutable (ไม่เปลี่ยนแปลง) โดยค่าเริ่มต้น นี่เป็นหนึ่งในหลาย ๆ วิธีที่ Rust ช่วยผลักดันคุณให้เขียนโค้ดที่มีความปลอดภัย และเหมาะกับการทำงานแบบขนาน (concurrency) ได้ง่ายขึ้น อย่างไรก็ตาม คุณยังคงสามารถเลือกที่จะทำให้ตัวแปรของคุณเป็น mutable (เปลี่ยนแปลงได้) ได้เช่นกัน มาสำรวจกันว่ายังไงและทำไม Rust จึงสนับสนุนการใช้ตัวแปรแบบ immutable และทำไมบางครั้งคุณอาจต้องการตัวแปรแบบ mutable
เมื่อตัวแปรเป็น immutable หลังจากกำหนดค่าให้ตัวแปรในครั้งแรกแล้ว
คุณไม่สามารถเปลี่ยนค่านั้นได้อีก เพื่อแสดงให้เห็นถึงแนวคิดนี้ สร้างโปรเจกต์ใหม่ชื่อ variables
ในโฟลเดอร์ projects โดยใช้คำสั่ง cargo new variables
จากนั้น ภายในโฟลเดอร์ variables เปิดไฟล์ src/main.rs และแทนที่โค้ดเดิมทั้งหมดด้วยโค้ดดังต่อไปนี้ ซึ่งจะยังไม่สามารถคอมไพล์ได้:
ชื่อไฟล์: src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
บันทึกและรันโปรแกรมโดยใช้คำสั่ง cargo run
คุณควรได้รับข้อความแสดงข้อผิดพลาดเกี่ยวกับ immutable
ดังที่แสดงในผลลัพธ์นี้:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
ตัวอย่างนี้แสดงให้เห็นว่าคอมไพเลอร์ช่วยคุณค้นหาข้อผิดพลาดในโปรแกรมของคุณได้อย่างไร ข้อผิดพลาดของคอมไพเลอร์อาจทำให้หงุดหงิด แต่จริง ๆ แล้วข้อผิดพลาดเหล่านี้หมายความว่าโปรแกรมของคุณยังไม่ปลอดภัยมากพอ ที่จะทำอะไรก็ตามที่คุณอยากทำ ซึ่งไม่ได้หมายความว่าคุณไม่ใช่โปรแกรมเมอร์ที่ดีแต่อย่างใด! Rustaceans ที่มีประสบการณ์ก็ยังคงได้รับข้อผิดพลาดจากคอมไพเลอร์
คุณได้รับข้อความแสดงข้อผิดพลาด cannot assign twice to immutable variable `x`
เนื่องจากคุณพยายามกำหนดค่าให้กับตัวแปร x
แบบ immutable เป็นครั้งที่สอง
เป็นเรื่องสำคัญที่เราควรได้รับข้อผิดพลาดขณะคอมไพล์ เมื่อพยายามเปลี่ยนค่าที่ถูกกำหนดให้เป็น immutable เพราะสถานการณ์เช่นนี้สามารถนำไปสู่ข้อผิดพลาดได้ หากโค้ดส่วนหนึ่งของเราทำงานบนสมมติฐานว่าค่าหนึ่งจะไม่เปลี่ยนแปลง แต่ส่วนอื่นของโค้ดเปลี่ยนค่าดังกล่าว โค้ดส่วนแรกอาจทำงานผิดพลาดจากที่ออกแบบไว้ สาเหตุของข้อผิดพลาดประเภทนี้มักเป็นเรื่องยากที่จะตรวจสอบย้อนหลัง โดยเฉพาะอย่างยิ่งเมื่อโค้ดส่วนที่สองเปลี่ยนค่าเพียงบางค่าเท่านั้น คอมไพเลอร์ Rust รับประกันว่า เมื่อคุณระบุว่าค่าหนึ่งจะไม่เปลี่ยนแปลง ค่านั้นจะไม่เปลี่ยนแปลงจริง ๆ โดยที่คุณไม่ต้องติดตามเช็คด้วยตัวเอง โค้ดของคุณจึงง่ายต่อการวิเคราะห์
แต่ mutable นั้นมีประโยชน์มาก และสามารถทำให้การเขียนโค้ดสดวกยิ่งขึ้น แม้ว่าตัวแปรจะเป็น immutable โดยค่าเริ่มต้น
แต่คุณสามารถกำหนดเป็น mutable โดยเพิ่ม mut
ไว้ข้างหน้าของชื่อตัวแปร ดังที่คุณได้ทำใน
บทที่ 2 การเพิ่ม mut
ยังสื่อความหมายให้กับผู้อ่านโค้ดในอนาคตด้วย
โดยระบุว่าส่วนอื่น ๆ ของโค้ดจะเปลี่ยนค่าของตัวแปรนี้ได้
ตัวอย่างเช่น ลองเปลี่ยน src/main.rs ให้เป็นตามนี้:
ชื่อไฟล์: src/main.rs
fn main() { let mut x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }
เมื่อเรารันโปรแกรมตอนนี้ เราจะได้ผลลัพธ์ดังนี้:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
เมื่อ mut
ถูกใช้ เราได้รับอนุญาตให้เปลี่ยนค่าของ x
จาก 5
เป็น 6
ท้ายที่สุดแล้วการตัดสินใจว่าจะใช้ mutable
หรือไม่นั้นขึ้นอยู่กับคุณและขึ้นอยู่กับสิ่งที่คุณคิดว่าชัดเจนที่สุดในสถานการณ์นั้น
ค่าคงที่
เช่นเดียวกับตัวแปร immutable ค่าคงที่คือค่าที่กำหนดให้กับตัวแปรและไม่อนุญาตให้เปลี่ยนแปลงค่า แต่มีความแตกต่างเล็กน้อยระหว่างค่าคงที่และตัวแปร
ข้อแรก คุณไม่สามารถใช้ mut
กับค่าคงที่ได้ ค่าคงที่ไม่เพียงเป็น immutable โดยค่าเริ่มต้นเท่านั้น
แต่มันเป็น immutable ตลอดไป คุณสามารถประกาศตัวแปรคงที่โดยใช้คีย์เวิร์ด const
แทนคีย์เวิร์ด
let
และต้องระบุประเภทตัวแปร เราจะอธิบายเกี่ยวกับประเภทและการระบุประเภทในหัวข้อถัดไป
“ประเภทข้อมูล” ดังนั้นไม่ต้องกังวลกับรายละเอียดในตอนนี้
เพียงรู้ว่าคุณต้องระบุประเภทตัวแปรให้กับตัวแปรคงที่เสมอ
ตัวแปรคงที่ สามารถประกาศในขอบเขตใดก็ได้ รวมถึงขอบเขตที่อยู่นอกสุด ซึ่งทำให้มีประโยชน์สำหรับค่าที่ต้องนำไปใช้กับโค้ดหลายส่วน
ข้อแตกต่างสุดท้ายคือ ตัวแปรคงที่ต้องระบุค่าที่ชัดเจนและคงที่เท่านั้น ไม่สามารถเป็นผลลัพธ์จากการคำนวณขณะรันโปรแกรม (runtime)
ต่อไปนี้เป็นตัวอย่างของการประการตัวแปรคงที่:
#![allow(unused)] fn main() { const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; }
ชื่อของตัวแปรคงที่คือ THREE_HOURS_IN_SECONDS
และค่าของมันมาจากผลลัพธ์การคูณ 60 (จำนวนวินาทีในหนึ่งนาที)
ด้วย 60 (จำนวนนาทีในหนึ่งชั่วโมง) ด้วย 3 (จำนวนชั่วโมงที่เราต้องการนับในโปรแกรมนี้)
ธรรมเนียมการตั้งชื่อของ Rust สำหรับค่าคงที่คือการใช้ตัวพิมพ์ใหญ่ทั้งหมด โดยมีเครื่องหมายขีดเส้นใต้ระหว่างคำ
คอมไพเลอร์สามารถประมวลผลหาค่าบางส่วนในขณะคอมไพล์
ซึ่งช่วยให้เราสามารถเขียนค่านี้ในลักษณะที่ง่ายต่อการเข้าใจ
แทนที่จะกำหนดค่าเป็น 10,800 ดูส่วน การประเมินค่าคงที่ของ Rust Reference
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการดำเนินการที่สามารถใช้เมื่อประกาศตัวแปรคงที่
ตัวแปรคงที่จะใช้ได้ตลอดระยะเวลาที่โปรแกรมรัน ภายในขอบเขตที่มีการประกาศตัวแปรคงที่ คุณสมบัตินี้ทำให้ตัวแปรคงที่มีประโยชน์สำหรับค่าในขอบเขตแอปพลิเคชั่นของคุณ ซึ่งหลายส่วนของโปรแกรมอาจจำเป็นต้องรู้ เช่น จำนวนคะแนนสูงสุดที่ผู้เล่นจะได้รับ หรือความเร็วแสง
การตั้งชื่อให้กับค่าที่ระบุโดยตรงในโค้ดซึ่งใช้งานทั่วทั้งโปรแกรมของคุณในรูปแบบค่าคงที่ มีประโยชน์ในการสื่อความหมายของค่านั้นให้กับผู้ดูแลโค้ดในอนาคต นอกจากนี้ยังช่วยให้คุณจำเป็นต้องแก้ไขโค้ดเพียงจุดเดียวเท่านั้น หากค่าคงที่นั้นจำเป็นต้องได้รับการเปลี่ยนแปลงในอนาคต
การบดบัง (Shadowing)
ดั่งที่คุณได้เห็นในบทช่วยสอนเกมทายตัวเลขใน บทที่ 2
คุณสามารถประกาศตัวแปรใหม่โดยใช้ชื่อเดียวกับตัวแปรก่อนหน้า Rustaceans บอกว่าตัวแปรแรกถูก บดบัง ด้วยตัวแปรตัวที่สอง
ซึ่งหมายความว่าตัวแปรตัวที่สองคือสิ่งที่คอมไพเลอร์จะเห็นเมื่อคุณเรียกใช้ชื่อของตัวแปร ผลก็คือ ตัวแปรตัวที่สองจะบดบังตัวแปรแรก
โดยนำการใช้ชื่อตัวแปรไปใช้กับตัวมันเอง จนกว่าตัวมันเองจะถูกบดบังไว้หรือสิ้นสุดขอบเขต
เราสามารถบดบังตัวแปรได้โดยการใช้ชื่อตัวแปรเดียวกัน และการใช้คีย์เวิร์ด let
ซ้ำ ดังต่อไปนี้:
ชื่อไฟล์: src/main.rs
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); } println!("The value of x is: {x}"); }
ขั้นแรก โปรแกรมนี้จะกำหนดให้ x
มีค่าเป็น 5
จากนั้นจะสร้างตัวแปรใหม่ชื่อ x
โดยการประกาศ let x =
ซ้ำ
และกำหนดค่าโดยนำค่าเดิมมาบวก 1
ดังนั้นค่าของ x
จึงเป็น 6
จากนั้น ภายในขอบเขตที่สร้างโดยการประกาศวงเล็บปีกกา
คำสั่ง let
ลำดับที่สามจะบดบัง x
และสร้างตัวแปรใหม่ให้มีค่าเป็น 12
ซึ่งเป็นผลลัพธ์จากการคูณค่าก่อนหน้าด้วย 2
เมื่อขอบเขตสิ้นสุดลง การบดบังที่อยู่ภายในจะสิ้นสุด และ x
จะกลับมามีค่าเป็น 6
เมื่อเรารันโปรแกรมนี้ จะได้ผลลัพธ์ดังต่อไปนี้:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
การบดบังแตกต่างจากการระบุตัวแปรเป็น mut
เนื่องจากเราจะได้รับข้อผิดพลาดขณะคอมไพล์หากเราพยายามกำหนดค่าตัวแปรนี้ใหม่อย่างไม่ตั้งใจ
โดยไม่ใช้คีย์เวิร์ด let
เมื่อใช้ let
เราสามารถทำการเปลี่ยนแปลงค่าได้เล็กน้อยโดยที่ตัวแปรจะยังคงเป็น immutable
หลังจากการเปลี่ยนแปลงเสร็จสิ้นแล้ว
ข้อแตกต่างอื่นระหว่าง mut
และ การบดบัง ก็คือ เนื่องจากเรากำลังสร้างตัวแปรใหม่อย่างมีประสิทธิภาพ
เมื่อเราใช้คีย์เวิร์ด let
อีกครั้ง เราสามารถเปลี่ยนประเภทของตัวแปรโดยที่ยังใช้ชื่อตัวแปรเดิมได้
ตัวอย่างเช่น สมมติว่าโปรแกรมขอให้ผู้ใช้ระบุจำนวนช่องว่างที่ต้องการแสดงผลระหว่างข้อความ โดยป้อนอักขระเว้นวรรค
และจากนั้นเราจะเก็บ input นั้นในรูปแบบตัวเลข:
fn main() { let spaces = " "; let spaces = spaces.len(); }
ตัวแปร spaces
ลำดับแรกมีประเภทเป็น string และตัวแปร spaces
ลำดับที่สองมีประเภทเป็นตัวเลข
การบดบังช่วยให้เราไม่ต้องคิดชื่อที่แตกต่างกัน เช่น spaces_str
และ spaces_num
;
แต่เราสามารถนำชื่อ spaces
ที่เรียบง่ายกว่ามาใช้ซ้ำได้
อย่างไรก็ตาม หากเราพยายามที่จะใช้ mut
สำหรับสิ่งนี้ เราจะได้รับข้อผิดพลาดขณะคอมไพล์ ดังที่แสดงต่อไปนี้:
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}
ข้อผิดพลาดแจ้งว่าเราไม่ได้รับอนุญาตให้เปลี่ยนประเภทของตัวแปร:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
ตอนนี้เราได้สำรวจวิธีการทำงานของตัวแปรแล้ว มาดูประเภทข้อมูลเพิ่มเติมที่ตัวแปรนั้นสามารถมีได้