Rust ภาษาแห่งกาลอนาคตที่มาถึงแล้ว กับการทดสอบไลบรารี Rust Geodesy (RG)

ผมศึกษาภาษาปาสคาลตอนเรียนอยู่มหาวิทยาลัยปีสี่อายุตอนนั้น 24 ปี เรียนภาษาปาสคาลด้วยตัวเองต่อมาใช้เขียน Traverse Pro ในขณะเดียวกันก็ศึกษา C ควบคู่ไปด้วยแต่ไม่ชำนาญ มาศึกษาไพทอนตอนอายุ 44 ปี ใช้ไพทอนเขียนโปรแกรมสำหรับ desktop ไว้หลายตัวเช่น Surveyor Pocket Tools สิบปีให้หลังอายุ 54 ปี ศึกษาภาษาดาร์ดกับฟลัตเตอร์เขียนแอพลงบนมือถือแอนดรอยด์หลายตัว เนื่องจากในยุคนี้ภาษาที่มาแรงที่สุดคือภาษา Rust ถ้าตาม timeline ผมน่าจะต้องรออายุ 64 ปีก่อนถึงค่อยศึกษา 🙂

แต่คงไม่ต้องรอนานขนาดนั้นเพราะโลกนี้ไม่แน่นอน ชีวิตยิ่งไม่แน่นอน เลยลงมือศึกษาภาษา Rust ที่ว่ากันว่ายากกกกกก ประมาณคล้ายกับการปีนหน้าผาชันๆ 90 องศาเลยทีเดียว

ย้อนรอยกำเนิดภาษา

ต้นกำเนิดภาษาในปี 2006 Graydon Hoare โปรแกรมเมอร์อายุ 29 ปีที่ทำงานให้กับ Mozilla ซึ่งเป็นบริษัทเบราว์เซอร์โอเพ่นซอร์ส เมื่อกลับถึงบ้านที่อพาร์ตเมนต์ของเขาในแวนคูเวอร์ ที่แคนาดา วันนั้นลิฟท์เสีย เขาเลยต้องเดินขึ้นอพาร์ทเมนต์ชั้นที่ 21 เขาทราบดีว่าโปรแกรมที่ใช้คุมลิฟท์เขียนด้วยภาษา C หรือ C++ นั้นเกิดแครช หลักๆของการแครชเกิดจากปัญหาหน่วยความจำ ทำให้ลิฟท์ไม่ทำงาน เขาตั้งปณิธานตอนเดินขึ้นบันไดว่าจะออกแบบและพัฒนาคอมไพเลอร์ภาษาใหม่ ให้มีความปลอดภัย

ภาษานี้เขาคาดหวังว่าจะให้เกิดภาษาการเขียนโปรแกรมใหม่ที่ปลอดภัยกว่า C++ และเน้นประสิทธิภาพในการทำงานที่สูง โดยไม่มีข้อบกพร่องในเรื่องหน่วยความจำโดยเขาได้ตั้งชื่อมันว่า Rust

Mozilla ได้นำไปใช้แทนโค้ดภาษา C++ เพื่อลดปัญหาบั๊กเกี่ยวกับหน่วยความจำใน Firefox Gecko และใช้โค้ดเพียงแค่ 85,000 บรรทัดจากเดิม 160,000 บรรทัด โดย Rust ถือเป็นภาษากำเนิดใหม่เพราะยังเป็นเวอร์ชัน 1.0 อยู่ในปี 2015 แต่ก็ได้รับความสนใจจากบริษัทยักษ์ใหญ่มากมายทั้ง Google, AWS และ Microsoft ที่สนใจนำไปใช้กับบางส่วนใน Windows หรือพัฒนา Azure Cloud

ก่อตั้ง Rust Foundation

อย่างไรก็ดีหลายบริษัทยังติดใจว่า Rust เป็นของ Mozilla ที่ไม่อิสระจริง ด้วยเหตุนี้เองจึงมีการจัดตั้ง Rust Foundation ขึ้นเพื่อเป็นทีมงานที่จะเข้ามาดูแลภาษา Rust อย่างเต็มตัวทั้งในเชิงของเงินสนับสนุนและกฏหมาย ตั้งแต่นั้นเป็นต้นมา Rust ก็ได้รับการปรับปรุงเวอร์ชั่นอย่างสม่ำเสมอ จนกลายเป็นหนึ่งในภาษาที่น่าจับตามอง และมีชุมชนโปรแกรมเมอร์ที่แข็งแรง

ในปี 2022-2023 Rust ได้รับการลงทุนและสนับสนุนจากบริษัทใหญ่ๆ อาทิ Amazon, Google และ Microsoft เพื่อให้ชุมชน Rust ต่อยอดประสิทธิภาพและการใช้งานในองค์กรขนาดใหญ่

ประสิทธิภาพเด่นของ Rust

Rust นั้นโดดเด่นในเรื่องของการจัดการหน่วยความจำที่ปลอดภัย โดยไม่ต้องพึ่งพา garbage collector ถือเป็นจุดเด่นที่ช่วยให้ภาษานี้ได้ออกแบบลูกเล่นการจัดการหน่วยความจำแบบแมนวลได้ง่ายขึ้น นอกจากนี้ยังมีระบบของเจ้าของตัวแปรและสามารถยืมตัวแปรที่เป็นนวัตกรรมเฉพาะของ Rust ทำให้การเขียนโปรแกรมแบบมัลติเทรดทำได้ง่ายขึ้นและปลอดภัยมากขึ้น

ทดสอบโค้ดภาษา Rust ด้วย Rust Geodesy (RG)

เป็นธรรมเนียมของผมที่ใช้ไลบรารีด้านการแปลงพิกัด หาความสูงยีออยด์ ผมจะต้องนึกถึง PROJ เป็นลำดับแรก เมื่อต้องเปลี่ยนภาษาโปรแกรม ไลบรารี PROJ นี้พัฒนาด้วยภาษา C/C++ ถ้าใช้ไพทอนก็ใช้ไลบรารีนี้ผ่าน PyProj สบายๆครับ ถ้าเขียนแอพบนแอนดรอยด์หรือไอโอเอส ต้องเอาซอร์สโค้ดไปคอมไพล์ใหม่โดยเฉพาะซึ่งผมได้เขียนบทความไว้ก่อนๆว่าเป็นความวิบากยากลำบากพอสมควร

เนื่องจาก Rust เป็นสิ่งใหม่สำหรับผมและ PROJ นั้นใหญ่โตมาก ผมพยายามจะคอมไพล์ซอร์สโค้ดมาใช้ใน Rust ผ่านช่องทางที่มีคนทำไว้ให้ ก็พอทำได้อยู่ครับ ผ่านทาง WSL2 แต่ผมมองยาวกว่านั้นคือต้องการ PROJ ไปคอมไพล์ใช้บนไมโครคอนโทรลเลอร์ ESP32 ด้วย แต่ไม่สำเร็จครับ

ผมลองค้นดูไลบรารีตัวอื่นว่ามีอะไรบ้างที่พอจะใกล้เคียงกับ PROJ ก็มาเจอ RG พอดูคนพัฒนาก็เอะใจเป็นคุณ Thomas Knudsen ก็เป็นหนึ่งในคนระดับสำคัญที่พัฒนา PROJ ในตัว RG เองคุณโทมัสได้เขียนไลบรารีใหม่ด้วยภาษา Rust เพียวๆ ไม่มีไลบรารีภาษา C++ ของ PROJ มาเกี่ยวข้องอีกต่อไป เมื่อได้อ่านบทความของคุณโทมัสพบว่า ปัญหาที่เกิดกับ PROJ นั้นมีมากมาย เนื่องจากเป็นไลบรารีใหญ่ และคนใช้กันมาก การจะเปลี่ยนแปลงอะไรนั้นยุ่งยากมากทีเดียวจะกระทบกระเทือนมาก แทนที่จะแก้ไข PROJ ก็เลยมาเขียนเป็นไลบรารีใหม่ ตั้งชื่อว่า Rust Geodesy เรียกสั้นๆว่า RG

สถานะการใช้งาน Rust Geodesy

สิ่งที่ผมสะดุดกึกจนหัวคะมำคือตัวอย่างการใช้งานกับเอกสารวิธีการใช้มีน้อยมาก ตัวอย่างที่ให้มาก็ไม่เพียงพอ เลยต้องมาแกะเอง ลองผิดลองถูกอยู่นานเหมือนกัน ถ้าติดขัดอะไรสามารถเข้าไปถามคุณโทมัสได้ที่ github ในหัวข้อ issues ซึ่งถ้าอ่าน Readme คุณโทมัสบอกว่าการเอาไปใช้ต้องเป็นลักษณะ experimental คือลักษณะให้ทดลองใช้ก่อน หรือแบบผู้เชี่ยวชาญมาใช้งาน สำหรับผมไม่ใช่ผู้เชี่ยวชาญอะไร จึงต้องออกแรงมากเป็นพิเศษในการแกะไลบรารีมาใช้งาน

ติดตั้ง Rust บน WSL2

ผมจะพูดถึงการติดตั้ง Rust แบบพอสังเขป เพราะหาอ่านในเว็บอื่นได้หลากหลาย วิธีการติดตั้งไม่ได้ยุ่งยากอะไร เปิด WSL2 มาพิมพ์คำสั่งตามลิสต์ข้างล่าง

sudo apt install build-essential # Install pre-reqs
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env #Import the environment config for rust
rustc --version

ถ้าติดตั้งสำเร็จ ลองใช้คำสั่ง rustc –version เพื่อดูเวอร์ชั่นของ Rust ที่ผมใช้อยู่เป็นรุ่น 1.81.0 จะแสดงผล rustc 1.81.0-nightly (02368e90f 2024-09-03) (1.81.0.0)

ลองใช้คำสั่ง hostnamectl เพื่อดูรุ่นของ WSL2 จะเห็นประมาณนี้

pbrobo@pbrworkstation:~$ hostnamectl
 Static hostname: pbrworkstation
       Icon name: computer-container
         Chassis: container
      Machine ID: ee9c155ee2ba479696a9873647fbdde5
         Boot ID: c839c8f2198c4f2ea638e8f14ce9605a
  Virtualization: wsl
Operating System: Ubuntu 22.04.5 LTS
          Kernel: Linux 5.15.153.1-microsoft-standard-WSL2
    Architecture: x86-64

ติดตั้งไลบรารี Rust Geodesy (RG)

สร้างไดเรคทอรีชื่อ “projects” แล้วใช้ cargo ตัวจัดการแพ็คเกจของ Rust สร้างโครงการชื่อ “rustgeodesy” ตัว cargo จะสร้างไดเรคทอรีให้พร้อมกับไฟล์ template ให้หลายไฟล์ สุดท้ายใช้คำสั่ง Code . เพื่อเรียก Visual Code (VSCode) ที่ผมใช้อยู่

mkdir projects
cd projects
cargo new rust-geodesy
cd rust-geodesy
code .

ตอนนี้ได้ template ของ Rust มา ต่อไปจะติดตั้งไลบรารี RG

cargo add geodesy

เมื่อ VSCode เปิดดูไฟล์ Cargo.toml จะแสดงไลบรารีหรือที่ Rust เองเรียกว่า Crate ดังนี้

[package]
name = "rust-geodesy"
version = "0.1.0"
edition = "2021"

[dependencies]
geodesy = "0.13.0"

แสดงว่าเป็น crate รุ่น 0.13.0 ต่อไปเป็นโค้ดแปลงพิกัดที่ผมจะนำมาทดสอบไลบรารี

โค้ดทดสอบการแปลงพิกัดจากค่าพิกัดกริดทีเอ็มเป็นค่าพิกัดภูมิศาสตร์

use geodesy::prelude::*;

fn main() -> Result<(), Box<Error>> {
    let mut context = Minimal::new();
    let tm100_7 = context.op("geo:in | tmerc lon_0=100.7 lat_0=0 k_0=1.0 x_0=500000 y_0=0 ellps=WGS84")?;
    let ne1 = Coor2D::raw(501261.22032610676, 1521088.326792507);
    let ne2 = Coor2D::raw(499315.16759512835, 1685050.54257971);
    let mut coor = [ne1, ne2];
    context.apply(tm100_7, Inv, &mut coor)?;
    println!("{:#?}", coor);
    Ok(())
}

โค้ดเริ่มจากกำหนดว่าเราจะใช้เครท RG คือ use geodesy::prelude::*

จากนั้นสร้างตัวแปร context ที่แก้ไขค่าได้จากเครทของ RG ดังนี้ let mut context = Minimal::new();

กำหนดการแปลงพิกัดผ่านสายอักขระ Pipeline

จากนั้นเราจะกำหนด operation (op) ให้กับตัวแปร context ในรูปอักขระ “geo:in | tmerc lon_0=100.7 lat_0=0 k_0=1.0 x_0=500000 y_0=0 ellps=WGS84” ดูเผินๆแล้วนึกถึงอักขระของ PROJ แต่มีความเรียบง่ายกว่า กระทัดรัดกว่า ในอักขระกำหนดให้เอา input “geo:in” เป็นค่าพิกัดภูมิศาสตร์หน่วยเป็นดีกรีเข้าด้านซ้าย ถ้าไม่กำหนดค่าพิกัดภูมิศาสตร์จะเป็นเรเดียนโดยอัตโนมัติ

สายอักขระจะเป็นลักษณะ pipeline คั่นด้วยขีดแนวดิ่ง (|) ประโยคที่สองเริ่มจากคำว่า “tmerc” คือกำหนดเส้นโครงแผนที่เป็น Transverse Mercator (TM) ตัวที่ตามมาเป็นพารามิเตอร์ของ TM ที่ต้องการคือ CM หรือ lon_0 = 100.7 lat_0=0 k_0=1.0 x_0=500000 y_0=0 ellps=WGS84 ปิดท้ายด้วย ellps = WGS84 บอกว่าใช้ทรงรีเป็น WGS84 ผลลัพธ์การคำนวณจะได้ค่าพิกัดเป็นกริดออกมาด้านขวา

การใช้คำว่า pipeline ถือว่าสื่อสารดีมาก ถ้า pipeline หมายถึงท่อที่ส่งน้ำหรือของเหลว ของเหลวจะไหลผ่านท่อหลายๆท่อหรือหลายขนาด จุดเปลี่ยนท่อคือจุดรับส่งข้อมูลกัน ถ้าท่อตัวที่สองต้องการค่าพิกัดกริด ปลายท่อตัวที่หนึ่งต้องส่งค่าพิกัดกริดไปให้ ถึงจะสื่อสารกันได้

บรรทัดต่อมาสองบรรทัดเราจะกำหนดค่าพิกัดกริดสองจุดมาเป็นตัวแปรแบบอาร์เรย์ที่แก้ไขค่าได้คือ let mut coor = [ne1, ne2]

การเลือกทิศทางการคำนวณ Pipeline

ที่ผมอธิบายบายไปข้างต้นถ้าป้อนค่าพิกัดภูมิศาสตร์หน่วยดีกรีเข้าด้านซ้ายจะได้ผลลัพธ์เป็นค่าพิกัดกริดด้านขวา แต่จะเห็นว่า input เราตรงกันข้าม เราป้อน input เป็นค่าพิกัดกริดจากด้านขวาแล้วต้องการค่าพิกัดภูมิศาสตร์เป็นผลลัพธ์มาออกด้านซ้าย ดังนั้นบรรทัดต่อไปที่สั่งประมวลผล context.apply เราจึงต้องกำหนดพารามิเตอร์ตัวที่สองเป็น “Inv” เพื่อคำนวณย้อนกลับ context.apply(tm100_7, Inv, &mut coor)? ข้อสังเกตพารามิเตอร์ที่สามเราใช้ &mut แสดงว่าค่าพิกัดกริดที่ส่งเข้าเมื่อโปรแกรมคำนวณแล้วจะส่งค่าพิกัดภูมิศาสตร์ออกมาแทนที่

ต่อไปจะคอมไพล์โปรแกรมด้วยคำสั่ง

cargo build

ผลลัพธ์เครื่องผมเป็นดังนี้

pbrobo@pbrworkstation:~/projects/rust-geodesy$ cargo build
   Compiling proc-macro2 v1.0.86
   Compiling unicode-ident v1.0.13
   Compiling libc v0.2.159
   Compiling utf8parse v0.2.2
   Compiling anstyle v1.0.8
   Compiling memchr v2.7.4
   Compiling anstyle-query v1.1.1
   Compiling is_terminal_polyfill v1.70.1
   Compiling colorchoice v1.0.2
   Compiling regex-syntax v0.8.4
   Compiling log v0.4.22
   Compiling heck v0.5.0
   Compiling anstyle-parse v0.2.5
   Compiling clap_lex v0.7.2
   Compiling strsim v0.11.1
   Compiling anyhow v1.0.89
   Compiling thiserror v1.0.64
   Compiling anstream v0.6.15
   Compiling option-ext v0.2.0
   Compiling cfg-if v1.0.0
   Compiling humantime v2.1.0
   Compiling float_eq v1.0.1
   Compiling clap_builder v4.5.18
   Compiling aho-corasick v1.1.3
   Compiling quote v1.0.37
   Compiling syn v2.0.79
   Compiling dirs-sys v0.4.1
   Compiling getrandom v0.2.15
   Compiling dirs v5.0.1
   Compiling uuid v1.10.0
   Compiling regex-automata v0.4.7
   Compiling regex v1.10.6
   Compiling clap_derive v4.5.18
   Compiling thiserror-impl v1.0.64
   Compiling env_filter v0.1.2
   Compiling env_logger v0.11.5
   Compiling clap v4.5.18
   Compiling clap-verbosity-flag v2.2.2
   Compiling geodesy v0.13.0
   Compiling rust-geodesy v0.1.0 (/home/pbrobo/projects/rust-geodesy)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.88s

ต่อไปผมรันโค้ดด้วย

cargo run

ผลลัพธ์การแปลงค่าพิกัด

ได้ผลลัพธ์เป็นค่าพิกัดภูมิศาสตร์หน่วยเป็นดีกรี เป็นค่าพิกัดที่อยู่บนทรงรี WGS84

pbrobo@pbrworkstation:~/projects/rust-geodesy$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/rust-geodesy`
[
    Coor2D(
        [
            13.753635230458237,
            100.71166197509885,
        ],
    ),
    Coor2D(
        [
            15.23552625325235,
            100.6936254255626,
        ],
    ),
]

ผมตรวจสอบด้วย Surveyor Pocket Tools กับแอพ SurveyStar Quick TM ทั้งสองนี้ใช้ไลบรารี PROJ ทั้งคู่และคำนวณผลลัพธ์มาได้ตรงกัน โค้ดที่ทดสอบนั้นสั้นๆง่ายๆ ตอนหน้าผมจะนำ Rust คอมไพล์ RG เพื่อลงสู่โลกแห่งไมโครคอนโทรลเลอร์กับ ESP32 ผมพยายามค้นดูในอินเทอร์เน็ตก็ยังไม่มีใครลองเอาไลบรารีนี้มาลงบน ESP32 ผมน่าจะคนแรก ตัวอย่างที่จะนำเสนอตอนต่อไปคือ การแปลงค่าพิกัดกริดทีเอ็มไปยังค่าพิกัดยูทีเอ็ม ลักษณะการคำนวณจะใช้สายอักขระ pipeline ที่ยาวขึ้น

ผมพยายามลอง PROJ เหมือนกันแต่ไม่สำเร็จ อย่าลืมว่าการคอมไพล์โค้ดบน Linux, Mac หรือวินโดส์จะเป็นการ cross compile เพื่อคอมไพล์แปลงโค้ดให้สามารถไปใช้บนสถาปัตยกรรมอื่น ในที่นี้คือ ESP32 ที่ใช้ซีพียูของ Xtensa ไม่ใช่เรื่องที่จะทำกันง่ายนัก

เป้าหมายของผมในอนาคตอาจจะมีการเขียนโปรแกรมเพื่อแปลงพิกัดต่างๆบน ESP32 จึงได้ทดสอบล่วงหน้า หรือถ้ามีใครที่ต้องการทำแบบนี้ก็จะเป็นแนวทางให้ สำหรับผมแล้ว ESP32 เป็นไมโครคอนโทรลเลอร์ของจีนแผ่นดินใหญ่ที่ดีมากและเหนือสิ่งอื่นใดคือราคาที่ถูกมาก สังเกตว่าผลิตภัณฑ์ RTK ของ Sparkfun ล้วนแล้วแต่ใช้ ESP32 จึงสามารถลดต้นทุนได้มาก มาขายในราคาที่ถูกแบบเหลือเชื่อในราคาหลักหมื่นต้นๆ

สำหรับภาษา Rust ที่ผมศึกษายังไม่ชำนาญคือเรียนยังไม่จบ หนทางยังยาวไกลอีกมาก มีอะไรที่ใหม่ไม่เหมือนภาษาอื่นที่ผมเคยศึกษามาเลย ตัวอย่างเช่นการเป็นเจ้าของตัวแปร (ownership) การยืมตัวแปร อายุไขตัวแปร ล้วนแต่เป็นสิ่งใหม่ที่ผมเจอ แล้วคำถามมันยากไหม ผมก็ตอบว่าสมคำร่ำลือครับ ยากกกก แต่ถ้าน้องๆนักศึกษาใช้ภาษานี้คล่อง งานที่รออยู่น่าจะเรียกเงินเดือนได้พอสมควร ตอนนี้บริษัทเทคใหญ่ๆหลายบริษัทได้เริ่มนำ Rust มาใช้งานกันแล้ว

เนื่องจากผมเรียนภาษานี้ไม่ได้ตั้งใจเอาไปประกอบอาชีพอะไร จึงไม่มีความกดดัน เอาความสนุกเป็นหลัก อ่านคู่มือบางครั้งอ่านแล้วอ่านอีกหลายสิบรอบก็ยังไม่เข้าใจ ก็ปิดเล่มไปแล้วไปทำอย่างอื่น บางครั้งกลับมาอ่านใหม่ก็เข้าใจดี ก็มาลองดูกันว่า Rust ภาษาแห่งอนาคตที่กาลเวลาได้มาถึงแล้ว จะสามารถขึ้นมาเป็นภาษาแนวหน้าจนสามารถมาเบียดตำแหน่งกับ C/C++ ได้หรือไม่ โปรดติดตามบทความตอนต่อไปครับ

Leave a Reply

Your email address will not be published. Required fields are marked *