اتحادها
اتحاد (Union) در کوروش یک نوع ترکیبی سطح پایین است که در آن تمام فیلدهای عضو آدرس حافظه پایه یکسانی را به اشتراک میگذارند. برخلاف struct که آفستهای مجزایی برای هر فیلد اختصاص میدهد، union روشی برای تفسیر یک بلوک واحد از حافظه خام به عنوان چندین نوع متفاوت فراهم میکند.
اندازه یک union با اندازه بزرگترین عضو آن تعیین میشود. نوشتن در هر فیلدی باعث همپوشانی و بازنویسی حافظه اشغالشده توسط سایر فیلدها میشود و عملاً مکانیزمی برای نامگذاری مستعار حافظه (memory aliasing) فراهم میکند.
اتحادها ایمن از نظر حافظه نیستند
اتحادها در کوروش ایمن از نظر حافظه نیستند. خواندن فیلدی که آخرین بار نوشته نشده است، رفتاری نامعین (undefined behavior) محسوب میشود، دقیقاً مانند زبان C. تنها زمانی از union استفاده کنید که بهطور مشخص به بازنمایی دادهای سطح پایین و بهینه از نظر حافظه نیاز دارید.
جایگزین ایمن
اگر به روشی ایمن از نظر حافظه برای ذخیره مقادیر انواع مختلف نیاز دارید، از enum استفاده کنید که در زمان کامپایل مشخص میکند کدام نوع فعال است.
تعریف یک Union
union DataUnion {
a: int;
b: float64;
}
fn main() {
var raw: DataUnion;
raw.b = 3.14;
}
استفاده از Union
میتوانید یک نمونه union ایجاد کرده و مستقیماً به فیلدها مقداردهی کنید:
fn main() {
var raw = DataUnion;
raw.a = 42; // مقداردهی فیلد صحیح
raw.b = 3.14; // بازنویسی همان حافظه با یک عدد اعشاری
}
پس از raw.b = 3.14، مقدار raw.a دیگر معتبر نیست.
مقداردهی اولیه Union
اتحادها را میتوان با استفاده از یک مقداردهنده اولیه union (Union Initializer) مقداردهی کرد و مشخص نمود که کدام فیلد در زمان ایجاد تنظیم شود:
var un: DataUnion = DataUnion { a: 10 };
قوانین:
- فقط یک فیلد باید مقداردهی شود.
- حافظه union مطابق با آن فیلد تنظیم خواهد شد.
موارد استفاده عملی
اتحادها ابزارهایی سطح پایین هستند که عمدتاً در برنامهنویسی سیستمی استفاده میشوند:
- نوعپانینگ (Type punning): تفسیر مجدد همان حافظه به عنوان انواع مختلف.
- ارتباط با کتابخانههای C: بسیاری از APIهای C در structهای خود از union استفاده میکنند.
- کارایی حافظه: زمانی که میدانید تنها یکی از چندین فیلد بزرگ در یک زمان استفاده خواهد شد.
مثال: تفسیر داده ۳۲ بیتی مشابه به صورت یک عدد صحیح یا بایتهای خام.
import std::libc{printf};
union IntBytes {
value: int;
bytes: uint8[4];
}
fn main() {
var data = IntBytes { value: 0x12345678 };
printf("%x %x %x %x\n", data.bytes[0], data.bytes[1], data.bytes[2], data.bytes[3]);
}
خروجی (در سیستمهای little-endian):
78 56 34 12
مقداردهی اولیه Union بدون نام
مشابه structها، میتوانید از unionهای بدون نام برای چیدمان داده درونخطی یا مقداردهی اولیه انواع union نامدار استفاده کنید. این مورد بهویژه برای بافرهای موقت سطح پایین مفید است.
union Payload {
i: int64;
s: char*;
}
pub fn main() {
const layout: Payload = union { s: "Cyrus!" };
printf("%s\n", layout.s);
}
فقط یک فیلد میتواند در یک مقدار union مقداردهی شود. ارائه چندین فیلد منجر به خطای زمان کامپایل خواهد شد.
نامگذاری مستعار اشارهگر در Union
از آنجا که هر فیلد در یک union آدرس پایه یکسانی را به اشتراک میگذارد، گرفتن ارجاع (reference) به یک فیلد خاص، یک اشارهگر تایپشده به بلوک حافظه مشترک union فراهم میکند. این امکان نامگذاری مستعار اشارهگر (pointer aliasing) را فراهم میسازد، به این معنا که میتوانید دادههای خام union را از طریق اشارهگرهایی از انواع مختلف دستکاری کنید.
این ویژگی قدرتمندی برای برنامهنویسی سیستمی است که امکان دستکاری مستقیم حافظه را بدون نیاز به cast صریح در هر مرحله فراهم میکند.
union DataStore {
p: char*;
i: int64;
}
pub fn main() {
// مقداردهی union از طریق فیلد اشارهگر
var inst = DataStore { p: null };
// دریافت اشارهگر به فیلد صحیح
// هر دو &inst.p و &inst.i به آدرس حافظه یکسانی اشاره میکنند
var iptr: int64* = &inst.i;
// تغییر غیرمستقیم حافظه union از طریق اشارهگر مستعار
*iptr = 2500;
// حافظه مشترک اکنون حاوی الگوی بیتی عدد صحیح ۲۵۰۰ است
printf("%d\n", inst.i);
}

