• ใน Zig เวอร์ชัน 0.15 ได้มีการนำ อินเทอร์เฟซ IO ใหม่ (std.Io.Reader, std.Io.Writer) เข้ามาใช้
  • มีเป้าหมายเพื่อปรับปรุงความซับซ้อนของวิธี IO แบบเดิมและปัญหาด้านประสิทธิภาพ แต่กลับเกิด ความสับสนในการใช้งานจริง
  • การใช้ tls.Client และ buffer มีรูปแบบการส่งพารามิเตอร์ที่ไม่สอดคล้องกัน จึงยิ่งเพิ่มความสับสน
  • แม้แต่การทำตัวอย่างการใช้งานพื้นฐานก็ยังมีข้อกำหนดที่ซับซ้อน เช่น การระบุ ขนาดบัฟเฟอร์หลายแบบและฟิลด์ออปชัน
  • เอกสารทางการ ตัวอย่างโค้ด และฟังก์ชันอำนวยความสะดวกยังมีไม่เพียงพอ ทำให้ ไม่เป็นธรรมชาติสำหรับผู้เริ่มต้น

อินเทอร์เฟซ IO ใหม่ที่นำมาใช้ใน Zig 0.15 และที่มา

  • ใน Zig เวอร์ชัน 0.15 ได้มีการนำประเภท IO ใหม่คือ std.Io.Reader และ std.Io.Writer เข้ามาใช้
  • อินเทอร์เฟซ IO แบบก่อนหน้าก่อให้เกิดความซับซ้อนจากปัญหาด้านประสิทธิภาพ การปะปนของประเภท และการใช้ anytype มากเกินไป
  • เป้าหมายหลักของโครงสร้าง IO ใหม่คือการแยกประเภทระหว่างอินเทอร์เฟซให้ชัดเจนและปรับปรุงประสิทธิภาพ

ปัญหาจริงในการใช้ tls.Client และอินเทอร์เฟซ IO

  • ระหว่างการอัปเดตไลบรารี smtp เดิม ได้เกิดความสับสนกับวิธีใช้ฟังก์ชัน tls.Client.init
  • ตามเอกสาร ฟังก์ชัน init ระบุว่าต้องรับ พอยน์เตอร์ของ Reader และ Writer พร้อมชุดออปชัน เป็นอาร์กิวเมนต์
  • net.Stream ของ Zig จะคืนค่า Stream.Reader/Writer ผ่านเมธอด reader() และ writer() ตามลำดับ
    • แต่ Stream.Reader/Writer กับ std.Io.Reader/Writer ไม่ใช่ประเภทเดียวกันแบบตรงตัว จึงต้องมีการแปลง
    • ฝั่ง Reader ต้องเรียกเมธอด interface() ส่วน Writer ต้องใช้ฟิลด์ &interface จึงดูขาดความสอดคล้อง

ปัญหาเรื่องการตั้งค่าบัฟเฟอร์และฟิลด์ออปชัน

  • stream.writer, stream.reader ต่างก็รับบัฟเฟอร์เป็นอาร์กิวเมนต์
    • Buffer ถูกเน้นว่าเป็นองค์ประกอบจำเป็นในอินเทอร์เฟซ IO ใหม่นี้
  • เมื่อเรียก tls.Client.init จำเป็นต้องมี ฟิลด์ออปชัน 4 รายการ ได้แก่ ca_bundle, host, write_buffer, read_buffer
    • กฎในการแยกค่าว่าบางส่วนส่งผ่านพารามิเตอร์ออปชัน และบางส่วนส่งเป็นอาร์กิวเมนต์โดยตรง ให้ความรู้สึกว่าไม่ชัดเจน
var tls_client = try std.crypto.tls.Client.init(
  reader.interface(),
  &writer.interface,
  .{
    .ca = .{.bundle = bundle},
    .host = .{ .explicit = "www.openmymind.net"; } ,
    .read_buffer = &read_buf2,
    .write_buffer = &write_buf2,
  },
)
  • ในทางปฏิบัติ หากไม่ได้ส่งพอยน์เตอร์ของบัฟเฟอร์อย่างถูกต้อง โปรแกรมอาจทำงานผิดปกติ หรือเกิดอาการค้างและล่มได้หลากหลายรูปแบบ

ปัญหาด้านความเป็นธรรมชาติเมื่อใช้ Reader

  • แม้ฟิลด์ reader ของ tls.Client เองจะเป็น "สตรีมที่ถอดรหัสแล้ว" แต่ใน std.Io.Reader กลับไม่มีเมธอด read แบบทั่วไป
  • กลับมีเพียงเมธอดที่เข้าใจได้ยากกว่า เช่น peek, takeByteSigned, readSliceShort
  • API ที่พอใกล้เคียงกับการใช้งานทั่วไปมากที่สุดคือการอ่านข้อมูลเข้าไปยังบัฟเฟอร์ผ่านเมธอด stream
var buf: [1024]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const n = try tls_client.reader.stream(&w, .limited(buf.len));

ตัวอย่างโค้ดทั้งหมดและปัญหาในการใช้งานจริง

  • ต่อให้พยายามทำตัวอย่างขั้นต่ำสุดที่ทำงานได้จริง ก็ยังมีหลายจุดที่ต้องใส่ใจ เช่น ออปชัน ขนาดบัฟเฟอร์ และการแปลงประเภท
  • การขาดแคลนการทดสอบ เอกสาร และตัวอย่าง ทำให้ความยากในการเรียนรู้และกำแพงในการเริ่มต้นสูงขึ้น
  • หากยังไม่เข้าใจเรื่องความสอดคล้องภายในภาษา Zig หรือแนวคิดเบื้องหลังการออกแบบ ก็มีหลายจุดที่ให้ความรู้สึกแปลก
  • แม้แต่ใน standard library เอง แนวทางนี้ก็ยังไม่ได้ถูกใช้อย่างแพร่หลาย จึงมีข้อมูลอ้างอิงสำหรับงานจริงค่อนข้างน้อย

ประสบการณ์และข้อสรุป

  • การเปลี่ยนชื่ออย่าง std.fmt.printInt รวมถึงการเปลี่ยนแปลงด้านการออกแบบ API ทำให้กระบวนการ migration ไม่ใช่เรื่องง่าย
  • ได้พบความยากซ้ำ ๆ หลายจุด เช่น รูปแบบ reader.interface(), &writer.interface, วิธีส่งออปชัน และความจำเป็นต้องใช้บัฟเฟอร์หลายชุด
  • สำหรับผู้ที่ไม่คุ้นเคยกับโปรโตคอลเครือข่ายหรือความปลอดภัยอย่าง TLS การทำความเข้าใจข้อกำหนดยิ่งรู้สึกยากขึ้นไปอีก
  • โดยสรุปแล้ว เมื่อเทียบกับเดิม ยังมีหลายส่วนที่ ขาดความชัดเจน การจัดทำเอกสาร และความสะดวกในการใช้งาน

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น