1 คะแนน โดย GN⁺ 2024-07-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

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 ความคิดเห็น

 
GN⁺ 2024-07-22
ความคิดเห็นจาก Hacker News
  • เหตุผลที่ Pin เข้าใจยากก็เพราะเอกสารทางการไม่ได้อธิบายอย่างชัดเจน

    • เอกสารอ้างว่า "Pin รับประกันว่าอ็อบเจ็กต์จะไม่ถูกย้ายตำแหน่งเด็ดขาด" แต่จริง ๆ แล้วไม่เป็นความจริง
    • อ็อบเจ็กต์ทั่วไปส่วนใหญ่เป็น Unpin ดังนั้น Pin จึงมักไม่มีบทบาทอะไรเลย
    • ชุดของชนิด T ที่ Pin ใช้งานได้จริงนั้นค่อนข้างเฉพาะมาก และเอกสารก็ไม่ได้เน้นจุดนี้มากพอ
  • Pin เข้าใจยากเพราะตัวมันเองไม่ได้มีความหมายในตัวเอง

    • ในกรณีของ Pin ทั้งภาษาและไลบรารีมาตรฐานไม่ได้บอกว่า Pin ทำอะไรได้และทำอะไรไม่ได้
    • แต่ผู้ให้บริการ InnerType จะสร้างเมธอดและ API เพิ่มเติมขึ้นมาเอง (ซึ่งภายในเป็นแบบ unsafe) เพื่อให้จัดการอ็อบเจ็กต์ที่ถูก pin ไว้ได้
    • จุดประสงค์เดียวของ Pin เองคือการให้พอยน์เตอร์ที่มี "ความสามารถที่มีอยู่โดยกำเนิด" น้อยลง
  • ควรเพิ่มคำว่า "rust" ไว้ในชื่อเรื่องเพื่อจะได้รู้ว่าบทความพูดถึงอะไร

  • คำว่า "value identity" ไม่ได้ถูกนิยามไว้ในเอกสารของ Mojo ที่ไหนเลย

    • แนะนำบรรยายของ Dave Abrahams เรื่อง "Value Semantics: Safety, Independence, Projection, & Future of Programming"
  • Pin เป็นตัวอย่างที่ดีของชื่อที่ถูกต้องในเชิงเทคนิค แต่เข้าใจยาก

    • Drop มีความหมายที่คุ้นเคยกว่า แต่ "pinning" ไม่เป็นเช่นนั้น
    • immovable!(…) อาจจะดีกว่า แต่ก็คิดชื่อที่ดีกว่านี้ยาก
    • ชื่อแบบอธิบายตรง ๆ เช่น prevent_moving!(…) และเทรต PreventMove อาจจะดีกว่า
  • ถ้าภาษาที่คล้าย Rust มี move-constructors ความจำเป็นของ Pin ก็อาจหายไป

    • เพราะผู้ใช้จะไม่มีวิธีทำลายอ็อบเจ็กต์ จึงไม่มีวิธีย้ายมันได้เช่นกัน
  • สามารถย้ายอ็อบเจ็กต์ผ่านการอ้างอิง &mut ได้ด้วย mem::swap/replace แต่ในทางปฏิบัติแทบไม่ค่อยจำเป็น

    • อยากให้มีวิธีเลือกใช้ move-reference ได้
    • การทำให้ swap และ replace เป็น unsafe อาจช่วยแก้ปัญหาได้
  • WithoutBoats กำลังถกเถียงกันอย่างเข้มข้นในประเด็น async iterators, poll และ pin

    • แทบไม่มีคอมมูนิตี้ไหนที่พูดคุยรายละเอียดเชิงลึกของภาษาอย่างเปิดเผยแบบนี้ และน่าติดตามมาก
  • Pinning/!Move มีประโยชน์กับงานหลายอย่างนอกเหนือจาก async/await

    • แต่ Rust จัดการเรื่องนี้ได้ไม่ดีนัก ดังนั้นคำตอบที่มักได้ยินคือ "เขียนโปรแกรมใหม่ด้วยภาษาอื่นเสียเลย"