Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

static mut Referanslarını Yasaklama

Özet

  • static_mut_refs lint düzeyi artık varsayılan olarak denydir. Bu lint, bir static mut için paylaşımlı ya da değiştirilebilir referans alınıp alınmadığını denetler.

Ayrıntılar

static_mut_refs lint’i, bir static mut değerine referans alınmasını tespit eder. 2024 sürümünde bu lint, bu tür referanslardan kaçınmanız gerektiğini vurgulamak için varsayılan olarak deny haline getirildi.

#![allow(unused)]
fn main() {
static mut X: i32 = 23;
static mut Y: i32 = 24;

unsafe {
    let y = &X;             // HATA: değiştirilebilir static'e paylaşımlı referans
    let ref x = X;          // HATA: değiştirilebilir static'e paylaşımlı referans
    let (x, y) = (&X, &Y);  // HATA: değiştirilebilir static'e paylaşımlı referans
}
}

Rust’ın değiştirilebilirlik XOR takma ad kuralını ihlal ederek böyle bir referansı almak, referans hiç okunmasa ya da yazılmasa bile, her zaman anında tanımsız davranış olmuştur. Dahası, bir static mut için bu kuralı sağlamak, kod hakkında küresel ölçekte akıl yürütmeyi gerektirir; bu da yeniden giriş ve/veya çok iş parçacıklılığı söz konusu olduğunda özellikle zordur.

Görünür bir & işleci olmadan örtük referansların otomatik oluşturulduğu bazı durumlar da vardır. Örneğin aşağıdaki kullanımlar da lint’i tetikler:

#![allow(unused)]
fn main() {
static mut NUMS: &[u8; 3] = &[0, 1, 2];

unsafe {
    println!("{NUMS:?}");   // HATA: değiştirilebilir static'e paylaşımlı referans
    let n = NUMS.len();     // HATA: değiştirilebilir static'e paylaşımlı referans
}
}

Alternatifler

Mümkün olan her durumda, bunun yerine yerel olarak akıl yürütülebilen bir soyutlama arkasında içsel değiştirilebilirlik sağlayan bir türün değiştirilemez static halini kullanmanız şiddetle tavsiye edilir. Bu yaklaşım, Rust’ın değiştirilebilirlik XOR takma ad kuralını korumanın karmaşıklığını büyük ölçüde azaltır.

Yerel olarak akıl yürütülebilen bir soyutlamanın mümkün olmadığı ve bu yüzden static değişkeninize erişimler hakkında hâlâ küresel düzeyde düşünmek zorunda kaldığınız durumlarda, artık &raw const veya &raw mut işleçleri ile elde edilenler gibi ham işaretçiler kullanmalısınız. Doğrudan referans almak yerine önce ham işaretçi elde etmek, o işaretçi üzerinden yapılacak erişimlerin güvenlik gereksinimlerini unsafe geliştiricileri için daha tanıdık hale getirir ve bunları daha küçük kod bölgelerine ertelemeyi veya sınırlamayı sağlar.

Aşağıdaki örneklerin yalnızca fikir vermek için bulunduğunu unutmayın; bunlar tam teşekküllü uygulamalar değildir. Bunları olduğu gibi kopyalamayın. Kendi durumunuz, ihtiyaçlarınıza göre değişiklik yapmanızı gerektirecek ayrıntılar içerebilir. Bu örnekler, soruna yaklaşmanın farklı yollarını göstermek içindir.

Standart kütüphanedeki ilgili türlerin belgelerini, tanımsız davranış referansını, Rustonomicon’u okumanız ve sorularınız varsa Users Forum gibi Rust forumlarından birine danışmanız önerilir.

Globalleri kullanmayın

Muhtemelen bunu zaten biliyorsunuz; ama mümkünse değiştirilebilir genel durumdan kaçınmak en iyisidir. Elbette bu bazen biraz hantal ya da zor olabilir; özellikle birçok fonksiyon arasında değiştirilebilir referans taşımak gerekiyorsa.

Atomics

atomik türler, static içinde (mut olmadan) kullanılabilecek tamsayılar, işaretçiler ve boole türleri sağlar.

use std::sync::atomic::Ordering;
use std::sync::atomic::AtomicU64;

// Şundan:
//   static mut COUNTER: u64 = 0;
// buna geçin:
static COUNTER: AtomicU64 = AtomicU64::new(0);

fn main() {
    // Kullanım durumunuzu analiz edip doğru Ordering seçtiğinizden emin olun.
    COUNTER.fetch_add(1, Ordering::Relaxed);
}

Mutex or RwLock

Türünüz bir atomikten daha karmaşıksa, genel değere doğru erişimi sağlamak için Mutex ya da RwLock kullanmayı düşünün.

use std::sync::Mutex;
use std::collections::VecDeque;

// Şundan:
//     static mut QUEUE: VecDeque<String> = VecDeque::new();
// buna geçin:
static QUEUE: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());

fn main() {
    QUEUE.lock().unwrap().push_back(String::from("abc"));
    let first = QUEUE.lock().unwrap().pop_front();
}

OnceLock or LazyLock

Eğer static mut kullanma nedeniniz const olamayan tek seferlik bir ilk kurulumsa, bunun yerine OnceLock ya da LazyLock kullanabilirsiniz.

use std::sync::LazyLock;

struct GlobalState;

impl GlobalState {
    fn new() -> GlobalState {
        GlobalState
    }
    fn example(&self) {}
}

// Şunun gibi geçici ya da başlatılmamış bir tür yerine:
//     static mut STATE: Option<GlobalState> = None;
// şunu kullanın:
static STATE: LazyLock<GlobalState> = LazyLock::new(|| {
    GlobalState::new()
});

fn main() {
    STATE.example();
}

OnceLock, LazyLock ile benzerdir; ancak kurucuya bilgi geçirmeniz gerekiyorsa kullanılabilir. Bu, main gibi tekil ilk kurulum noktalarıyla veya girdiler genel değere erişilen her yerde mevcutsa iyi çalışır.

use std::sync::OnceLock;

struct GlobalState;

impl GlobalState {
    fn new(verbose: bool) -> GlobalState {
        GlobalState
    }
    fn example(&self) {}
}

struct Args {
    verbose: bool
}
fn parse_arguments() -> Args {
    Args { verbose: true }
}

static STATE: OnceLock<GlobalState> = OnceLock::new();

fn main() {
    let args = parse_arguments();
    let state = GlobalState::new(args.verbose);
    let _ = STATE.set(state);
    // ...
    STATE.get().unwrap().example();
}

no_std tek seferlik ilk kurulum

Bu örnek, genel bir değerin tek seferlik başlatılmasını sağlama açısından OnceLock benzeridir; ama std gerektirmez, bu da no_std bağlamında yararlıdır. Hedefiniz atomikleri destekliyorsa genel değerin başlatılıp başlatılmadığını denetlemek için bir atomik kullanabilirsiniz. Örnek desen şuna benzer:

use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;

struct Args {
    verbose: bool,
}
fn parse_arguments() -> Args {
    Args { verbose: true }
}

struct GlobalState {
    verbose: bool,
}

impl GlobalState {
    const fn default() -> GlobalState {
        GlobalState { verbose: false }
    }
    fn new(verbose: bool) -> GlobalState {
        GlobalState { verbose }
    }
    fn example(&self) {}
}

const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;

static STATE_INITIALIZED: AtomicUsize = AtomicUsize::new(UNINITIALIZED);
static mut STATE: GlobalState = GlobalState::default();

fn set_global_state(state: GlobalState) {
    if STATE_INITIALIZED
        .compare_exchange(
            UNINITIALIZED,
            INITIALIZING,
            Ordering::SeqCst,
            Ordering::SeqCst,
        )
        .is_ok()
    {
        // GUVENLIK: STATE üzerindeki okuma/yazmalar INITIALIZED korumasıyla korunuyor.
        unsafe {
            STATE = state;
        }
        STATE_INITIALIZED.store(INITIALIZED, Ordering::SeqCst);
    } else {
        panic!("zaten baslatilmis ya da eszamanli baslatma var");
    }
}

fn get_state() -> &'static GlobalState {
    if STATE_INITIALIZED.load(Ordering::Acquire) != INITIALIZED {
        panic!("baslatilmamis");
    } else {
        // GUVENLIK: Durum baslatildiktan sonra degistirilebilir erisim mumkun degil.
        unsafe { &*&raw const STATE }
    }
}

fn main() {
    let args = parse_arguments();
    let state = GlobalState::new(args.verbose);
    set_global_state(state);
    // ...
    let state = get_state();
    state.example();
}

Bu örnek, static içine başlatılmadan önce bir varsayılan değer koyabildiğinizi varsayar; burada örneğin const default kurucusu kullanılıyor. Bu mümkün değilse MaybeUninit, ya da dinamik trait gönderimi (trait uygulayan sahte bir türle) veya varsayılan yer tutucu sağlayan başka bir yaklaşım düşünebilirsiniz.

Topluluk tarafından sunulan, benzer tek seferlik ilk kurulum sağlayan crate’ler de vardır. Örneğin static-cell crate’i, portable-atomic kullanarak atomik olmayan hedefleri de destekler.

Ham işaretçiler

Bazı durumlarda static mut kullanmaya devam edebilir, ama referans oluşturmaktan kaçınabilirsiniz. Örneğin yalnızca bir C kütüphanesine [ham işaretçi] geçirmeniz gerekiyorsa ara referans oluşturmayın. Bunun yerine [ham ödünç alma işleçlerini] kullanabilirsiniz:

#[repr(C)]
struct GlobalState {
    value: i32
}

impl GlobalState {
    const fn new() -> GlobalState {
        GlobalState { value: 0 }
    }
}

static mut STATE: GlobalState = GlobalState::new();

unsafe extern "C" {
    fn example_ffi(state: *mut GlobalState);
}

fn main() {
    unsafe {
        // Şundan:
        //     example_ffi(&mut STATE as *mut GlobalState);
        // buna geçin:
        example_ffi(&raw mut STATE);
    }
}

Yine de değiştirilebilir işaretçiler etrafındaki takma ad kısıtlarını korumanız gerektiğini unutmayın. Bunun için iş parçacıkları, kesme işleyicileri ve yeniden giriş arasında kullanım biçimine dair iç ya da dış eşzamanlama veya kanıtlar gerekebilir.

Sync ile UnsafeCell

UnsafeCell Sync uygulamaz; bu yüzden bir static içinde kullanılamaz. İçsel değiştirilebilirlik sağlamak için UnsafeCell etrafında kendi sarmalayıcınızı oluşturup ona Sync uygulaması ekleyebilirsiniz. Bu yaklaşım, değiştirilebilir işaretçiler için gereken güvenlik değişmezlerini sağlayan dış kilitleriniz ya da başka garantileriniz varsa yararlı olabilir.

Bunun büyük ölçüde ham işaretçiler örneğiyle aynı olduğunu unutmayın. Sarmalayıcı, türü nasıl kullandığınızı daha görünür kılar ve hangi güvenlik koşullarına dikkat etmeniz gerektiğine odaklanmanıza yardım eder. Onun dışında yaklaşım kabaca aynıdır.

#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;

fn with_interrupts_disabled<T: Fn()>(f: T) {
    // Gerçek bir örnek kesmeleri kapatırdı.
    f();
}

#[repr(C)]
struct GlobalState {
    value: i32,
}

impl GlobalState {
    const fn new() -> GlobalState {
        GlobalState { value: 0 }
    }
}

#[repr(transparent)]
pub struct SyncUnsafeCell<T>(UnsafeCell<T>);

unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}

static STATE: SyncUnsafeCell<GlobalState> = SyncUnsafeCell(UnsafeCell::new(GlobalState::new()));

fn set_value(value: i32) {
    with_interrupts_disabled(|| {
        let state = STATE.0.get();
        unsafe {
            // GUVENLIK: Bu deger yalnizca kesme isleyicisinde okunuyor,
            // kesmeler kapatildi ve bunu yalnizca tek bir is parcaciginda kullaniyoruz.
            (*state).value = value;
        }
    });
}
}

Standart kütüphanede UnsafeCell’in SyncUnsafeCell adında yalnızca nightly’de bulunan kararsız bir çeşidi vardır. Yukarıdaki örnek standart kütüphane türünün çok sadeleştirilmiş bir sürümünü gösterir; ama kullanım mantığı benzerdir. Daha iyi yalıtım sağlayabilir, bu yüzden ayrıntılar için gerçek uygulamasına bakmak faydalıdır.

Bu örnek, gömülü ortamlarda görebileceğiniz türden hayalî bir with_interrupts_disabled fonksiyonu içerir. Örneğin critical-section crate’i, gömülü ortamlar için benzer bir işlevsellik sağlar.

Güvenli referanslar

Bazı durumlarda bir static mut için referans oluşturmak güvenli olabilir. static_mut_refs lint’inin asıl vurguladığı nokta, bunun doğru yapılmasının çok zor olmasıdır. Ama bu imkansız demek değildir. Eğer takma ad gereksinimlerinin korunduğunu garanti edebiliyorsanız, örneğin static değeri dar bir kapsamda kullanıyorsanız, iç ya da dış eşzamanlama sağladıysanız, kesme işleyicileri, yeniden giriş, panic güvenliği, drop işleyicileri gibi durumları hesaba kattıysanız, referans almak uygun olabilir.

Bunun için iki yaklaşım vardır. Ya static_mut_refs lint’ine izin verirsiniz (tercihen olabildiğince dar kapsamda), ya da &mut *&raw mut MY_STATIC örneğinde olduğu gibi ham işaretçileri referansa dönüştürürsünüz.

Kısa ömürlü referanslar

Bir static mut için referans oluşturmanız şartsa, bu referansın ne kadar süre var olduğunu mümkün olduğunca dar tutmanız önerilir. Referansı bir yerde saklamaktan ya da kodun büyük bir bölümü boyunca canlı tutmaktan kaçının. Kısa ömürlü tutmak, denetlemeyi ve erişimin tüm süre boyunca gerçekten tekil olduğunu doğrulamayı kolaylaştırır. Varsayılan birim olarak işaretçileri kullanın; yalnızca gerçekten gerekliyse işaretçiyi referansa çevirin.

Taşıma

static mut referanslarını düzeltmek için otomatik taşıma yoktur. Tanımsız davranıştan kaçınmak için kodunuzu Alternatifler bölümünde önerildiği gibi farklı bir yaklaşımla yeniden yazmanız gerekir.