เทคนิคการแชร์ข้อมูลแบบแก้ไขได้ระหว่าง Rust และ Python ด้วย PyO3
(blog.lilyf.org)บทสรุป (Conclusion)
การที่ PyO3 ไม่สามารถเปิดเผย struct ที่ใช้ lifetime ของ Rust ให้กับ Python ได้โดยตรง อาจดูเหมือนเป็นข้อจำกัดในตอนแรก แต่ไลบรารีมาตรฐานของ Rust และ PyO3 มีเครื่องมือทรงพลังที่ช่วยก้าวข้ามข้อจำกัดนี้ได้ std::mem::take และ std::mem::replace ช่วยให้จัดการทั้ง mutable reference และ owned value ได้อย่างคล่องตัว ขณะที่ Arc และ Mutex มีประโยชน์อย่างมากในการเปิดเผยข้อมูลที่แชร์ร่วมกันและแก้ไขได้ให้กับ Python โดยเฉพาะ MutexExt ของ PyO3 ซึ่งเป็นเครื่องมือสำคัญในการป้องกัน deadlock เมื่อต้องใช้ mutex ร่วมกับ Python
สรุปประเด็นสำคัญ
เอกสารนี้อธิบายปัญหาทางเทคนิคที่พบและกระบวนการแก้ไขทีละขั้นตอน ระหว่างการแชร์ข้อมูลแบบ mutable ระหว่าง Rust และ Python ในโปรเจ็กต์ที่นำภาษาเทมเพลตของ Django มาเขียนใหม่ด้วย Rust
-
พื้นหลัง: ภาษาเทมเพลตของ Django ใช้อ็อบเจ็กต์ชื่อ
contextเพื่อส่งข้อมูลแบบไดนามิกให้กับเทมเพลต ใน implementation ฝั่ง Rust ของโปรเจ็กต์นี้ ได้กำหนดcontextนี้เป็น struct ของ Rust และจำเป็นต้องส่งต่อเป็น mutable reference (&mut Context) เมื่อทำการเรนเดอร์ template tag -
ปัญหาเริ่มต้น: ต้องส่ง mutable reference (
&mut Context) จากโค้ด Rust ไปยังฟังก์ชัน Python เพื่อรัน custom tag แต่ Python ไม่เข้าใจ lifetime ของ Rust และ PyO3 ซึ่งเป็นไลบรารีเชื่อมต่อ Rust-Python ก็ต้องการ owned value ทำให้เมื่อพยายามส่ง reference โดยตรงจะเกิด compile error -
กระบวนการแก้ปัญหา:
- แก้ปัญหาเรื่อง ownership: ใช้
std::mem::takeเพื่อดึง ownership ออกจาก&mut Contextชั่วคราว แล้วสร้างอ็อบเจ็กต์Contextแบบ owned ที่สามารถส่งให้ Python ได้ หลังจากโค้ด Python ทำงานเสร็จ ก็พยายามใช้std::mem::replaceเพื่อนำContextที่ผ่านการประมวลผลแล้วกลับไปไว้ยังตำแหน่ง reference เดิม - แก้ error แบบ 'Moved Value': อย่างไรก็ตาม ในกระบวนการนี้ เมื่ออ็อบเจ็กต์
Contextถูก move เข้าไปยังฟังก์ชัน Python แล้วพยายามนำกลับมาใช้อีก จะเกิด compile error ว่า "use of moved value" เพื่อแก้ปัญหานี้ จึงนำArc(Atomic Reference Count) มาใช้ห่อContextทำให้สามารถส่ง reference ที่คัดลอกมา (clone) ไปยัง Python ได้โดยไม่ต้องย้าย ownership - การรับมือเมื่อ Python ยังคงเก็บ reference ไว้: หาก Python ยังคงถือ reference ของ
Contextเอาไว้ การกู้ ownership กลับด้วยArc::try_unwrapอาจล้มเหลว ในกรณีนี้จึงมีการทำ fallback method เช่นclone_refที่ทำ deep clone ของข้อมูลภายในContextเพื่อคัดลอกข้อมูลออกมา - อนุญาตให้ Python แก้ไขข้อมูลได้: ในขั้นสุดท้าย เพื่อให้โค้ด Python ไม่ได้มีแค่อ่าน
Contextแต่สามารถแก้ไขได้ด้วย จึงมีการนำMutexมาใช้ โดยใช้โครงสร้างArc<Mutex<Context>>เพื่อรับประกันว่าหลายเธรดจะเข้าถึงและแก้ไขข้อมูลได้อย่างปลอดภัย และเพื่อป้องกัน deadlock กับ Python interpreter จึงใช้เมธอดlock_py_attachedของMutexExtที่ PyO3 มีให้
- แก้ปัญหาเรื่อง ownership: ใช้
ยังไม่มีความคิดเห็น