static mut Referanslarını Yasaklama
Özet
static_mut_refslint düzeyi artık varsayılan olarakdenydir. Bu lint, birstatic mutiç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.