Pin
- ชนิด
Pin และแนวคิดเรื่อง pinning เป็นองค์ประกอบพื้นฐานของระบบนิเวศ async ของ Rust
- อย่างไรก็ตาม
Pin เป็นหนึ่งในสิ่งที่เข้าถึงได้ยากและมักถูกเข้าใจผิดได้ง่าย
- บทความนี้อธิบายว่า
Pin ช่วยให้บรรลุอะไร เกิดขึ้นมาได้อย่างไร และปัญหาของ Pin ในปัจจุบันคืออะไร
Requirements
- เพื่อรองรับการอ้างอิงในฟังก์ชัน async จึงจำเป็นต้องเก็บการอ้างอิงไว้ภายใน
Future
- ปัญหาคือการอ้างอิงเหล่านี้อาจเป็นการอ้างอิงแบบ self-referential ได้
- โค้ดตัวอย่าง:
async fn foo<'a>(z: &'a mut i32) { ... }
async fn bar(x: i32, y: i32) -> i32 {
let mut z = x + y;
foo(&mut z).await;
z
}
- สถานะภายในของ
Bar มีดังนี้:
enum Bar {
Start { x: i32, y: i32 },
FirstAwait { z: i32, foo: Foo<'?> },
Complete,
}
- เป้าหมายของ
Pin คือการจัดการชนิดแบบ self-referential ได้อย่างปลอดภัย
Non-solutions: move constructors and offset pointers
- move constructor และ offset pointer ใช้งานใน Rust ไม่ได้
- move constructor จะปรับพอยน์เตอร์ระหว่างการย้าย แต่ใน Rust ทำแบบนั้นไม่ได้
- offset pointer ก็ใช้ไม่ได้ เพราะตอนคอมไพล์ไม่สามารถรู้ได้ว่าการอ้างอิงนั้นเป็น self-reference หรือไม่
The “pinned typestate”
- วัตถุไม่ได้ถูกห้ามย้าย ตลอดเวลา แต่ต้องถูกห้ามย้ายตั้งแต่จุดเวลาหนึ่งเป็นต้นไป
- ในโมเดลของ Ralf Jung วัตถุจะเปลี่ยนจากสถานะ "owned" ไปเป็นสถานะ "shared" และต่อไปเป็นสถานะ "pinned"
- เมื่อเข้าสู่สถานะ pinned แล้ว วัตถุนั้นจะไม่สามารถถูกย้ายได้อีก
?Move
- ก่อนจะมี
Pin เคยมีความพยายามใช้แนวทางที่อิงกับเทรตใหม่ชื่อ Move
- ชนิดที่ไม่ได้ implement
Move จะเปลี่ยนเข้าสู่สถานะ pinned เมื่อมีการนำไปอ้างอิง
- แต่
Move ไม่สามารถรองรับ backward compatibility ได้
Pin
Pin ออกแบบชนิดการอ้างอิงแบบใหม่เพื่อทำให้วัตถุเข้าสู่สถานะ pinned
Pin ถูก implement เป็น library API จึงรักษา backward compatibility ได้
- มีการเพิ่ม auto trait
Unpin เพื่อให้ชนิดส่วนใหญ่ไม่ต้องแยกความแตกต่างระหว่างสถานะ pinned กับสถานะปกติ
The problems with Pin
Pin มีปัญหาหลายอย่างในด้านการใช้งาน
Pin ถูก implement เป็นชนิดในไลบรารี ทำให้ความสามารถหลายอย่างที่ชนิดการอ้างอิงทั่วไปมีหายไป
- ตัวอย่างเช่น
&mut T ไม่ได้ implement Copy แต่สามารถส่งเป็นอาร์กิวเมนต์ได้หลายครั้ง
Pin ไม่ได้มอบความสะดวกแบบนั้น
- การใช้
Pin จึงก่อให้เกิดความสับสนได้มาก
In my next post…
Pin ทำให้สามารถคอมไพล์การอ้างอิงแบบ arbitrary ในฟังก์ชัน async ได้อย่างปลอดภัย
- แต่
Pin ก็เพิ่มความซับซ้อนขึ้นด้วย และบทความถัดไปจะพูดถึงวิธีปรับปรุงสิ่งนี้
สรุปโดย GN⁺
Pin เป็นองค์ประกอบสำคัญของระบบนิเวศ async ของ Rust
- ปัญหาด้านการใช้งานของ
Pin มีสาเหตุจากการที่มันถูก implement เป็นชนิดในไลบรารี
- บทความถัดไปจะกล่าวถึงวิธีปรับปรุง
Pin
- โปรเจกต์ที่มีฟังก์ชันคล้ายกันคือ
pin-project-lite
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เหตุผลที่
Pinเข้าใจยากก็เพราะเอกสารทางการไม่ได้อธิบายอย่างชัดเจนPinรับประกันว่าอ็อบเจ็กต์จะไม่ถูกย้ายตำแหน่งเด็ดขาด" แต่จริง ๆ แล้วไม่เป็นความจริงUnpinดังนั้นPinจึงมักไม่มีบทบาทอะไรเลยTที่Pinใช้งานได้จริงนั้นค่อนข้างเฉพาะมาก และเอกสารก็ไม่ได้เน้นจุดนี้มากพอPinเข้าใจยากเพราะตัวมันเองไม่ได้มีความหมายในตัวเองPinทั้งภาษาและไลบรารีมาตรฐานไม่ได้บอกว่าPinทำอะไรได้และทำอะไรไม่ได้InnerTypeจะสร้างเมธอดและ API เพิ่มเติมขึ้นมาเอง (ซึ่งภายในเป็นแบบ unsafe) เพื่อให้จัดการอ็อบเจ็กต์ที่ถูก pin ไว้ได้Pinเองคือการให้พอยน์เตอร์ที่มี "ความสามารถที่มีอยู่โดยกำเนิด" น้อยลงควรเพิ่มคำว่า "rust" ไว้ในชื่อเรื่องเพื่อจะได้รู้ว่าบทความพูดถึงอะไร
คำว่า "value identity" ไม่ได้ถูกนิยามไว้ในเอกสารของ Mojo ที่ไหนเลย
Pinเป็นตัวอย่างที่ดีของชื่อที่ถูกต้องในเชิงเทคนิค แต่เข้าใจยากDropมีความหมายที่คุ้นเคยกว่า แต่ "pinning" ไม่เป็นเช่นนั้นimmovable!(…)อาจจะดีกว่า แต่ก็คิดชื่อที่ดีกว่านี้ยากprevent_moving!(…)และเทรตPreventMoveอาจจะดีกว่าถ้าภาษาที่คล้าย Rust มี move-constructors ความจำเป็นของ
Pinก็อาจหายไปสามารถย้ายอ็อบเจ็กต์ผ่านการอ้างอิง
&mutได้ด้วยmem::swap/replaceแต่ในทางปฏิบัติแทบไม่ค่อยจำเป็นswapและreplaceเป็น unsafe อาจช่วยแก้ปัญหาได้WithoutBoats กำลังถกเถียงกันอย่างเข้มข้นในประเด็น async iterators, poll และ pin
Pinning/
!Moveมีประโยชน์กับงานหลายอย่างนอกเหนือจาก async/await