Rust’ın Pin’ini anlamak
(without.boats)- Rust’taki
Pin, async/await’in oluşturduğu Future içinde kendine referans veren durumları güvenli biçimde ele almak için getirilmiş temel bir yapı taşıdır - async Future, her await noktasında durumu sakladığı için, bir nesnenin içindeki bir alanın aynı nesnenin başka bir alanına referans verdiği kendine referans veren tip haline gelebilir
- move constructor, offset pointer ve
?Movetasarımları; sırasıyla çalışma zamanı izleme maliyeti, derlenebilirlik ve mevcut API’lerle geriye dönük uyumluluk sorunları nedeniyle benimsenemedi - Nihai tasarım, işaretçiyi sararak hedefi pinned typestate durumuna sokan
Pin’dir;Unpinauto trait’i sayesinde çoğu tip eskisi gibi taşınabilir kalır Pin’in zorluğu, değişmezlik kavramının kendisinden çok kütüphane tipi olmasının sınırlarından kaynaklanır; reborrowing,Pin::set, pinned projection veDropile etkileşim kullanılabilirliği ciddi biçimde düşürür
Pin’i gerekli kılan sorun
- Rust async ekosisteminde
Pinve pinning temel altyapıdır; ancak async Rust öğrenenler için hâlâ zor ve çokça yanlış anlaşılan bir alandır Pin’in amacı, kullanıcıların yalnızca güvenli Rust ile doğrudan kendine referans veren tipler oluşturmasını sağlamak değildir- Amaç, derleyicinin async fonksiyonlardan ürettiği kendine referans veren Future’ların veya tokio gibi runtime’ların unsafe kodla oluşturduğu kendine referans veren tiplerin güvenli biçimde işlenmesini sağlamaktır
- Örnekteki
async fn bar,foo(&mut z).awaitnoktasındaz’yi vez’ye referans verenFooFuture’ını aynı Future durumu içinde birlikte saklamak zorundadır- Bu durumda Future nesnesinin içindeki bir alan, aynı nesnenin içindeki başka bir alana referans verir
- Böyle bir Future tipi kendine referans veren tip olur
- Nesne bu duruma girdikten sonra taşınırsa, iç referans eski bellek konumunu gösterir; bu konum artık ölü bellek olabilir ya da başka bir değer için yeniden kullanılmış olabilir
Pinöncesi Rust’ta, bir nesnenin sahipliği veya mutable reference’ı varsa nesne taşınabildiği için, belirli bir andan sonra taşımayı yasaklamayı ifade edecek bir yola ihtiyaç vardı
Çözüm olamayan yaklaşımlar
-
move constructor
- move constructor, bir değer taşındığında destructor gibi kod çalıştırıp kendine referans veren işaretçileri yeni konuma düzelten bir yöntemdir
- Rust’ta işaretçiler yalnızca taşınan değerin “içinde” bulunmaz; örneğin kendi durumunu gösteren işaretçilerin bir vektörü içinde de bulunabilir
- Bu tür işaretçilerin hepsini izlemek için sonuçta çöp toplama benzeri bir çalışma zamanı bellek yönetimi gerekir
- Rust erken dönemde move constructor’a sahip olmamaya karar verdi ve pek çok unsafe kod, değerlerin yalnızca bellek kopyasıyla taşınabileceği varsayımına dayanır
- move constructor’ı sonradan eklemek breaking change olurdu
-
offset pointer
- offset pointer, kendine referansları normal referans yerine kendine referans veren nesnenin adresine göre bir offset olarak derleme yöntemidir
- Derleme zamanında hangi referansın kendine referans olduğu her zaman belirlenemez
- Farklı dallarda aynı değer kendi nesnesinin içini de gösterebilir, dışını da gösterebilir
- Bunu ele almak için referansları offset ve reference’tan oluşan enum benzeri bir biçime derlemek gerekir; async/await çalışmaları sırasında bu gerçekçi olmayan bir yöntem olarak değerlendirildi
pinned typestate gereksinimleri
- Kendine referans veren Future’ın en baştan itibaren her zaman taşınamaz olması gerekmez; yaşam döngüsünün bir noktasına kadar serbestçe taşınabilmeli, belirli bir noktadan sonra ise taşınmamalıdır
- Future başka Future’larla birleştirilirken taşınabilir olmalıdır
- poll edileceği konuma yerleştirildikten sonra artık taşınmamalıdır
- Ralf Jung’un modeli, mevcut “owned” ve “shared” typestate’lerine ek olarak, kendine referans veren Future için üçüncü bir durum olan pinned typestate’i ekler
- Bir nesne pinned typestate’e girdiğinde bir daha taşınmamalıdır
- Daha kesin ifadeyle, önce destructor çalıştırılmadan o nesnenin belleği geçersiz kılınmamalıdır
- Pratikte bu, nesnenin yeni bir konuma taşınmaması gerektiği anlamına gelir
- Çoğu tip kendine referans içeremeyeceğinden pinned typestate onlar için özel bir anlam taşımaz
- Bu tiplerin pinning kısıtlarından çıkıp yeniden taşınabilir olması tercih edilir
- pinned typestate’in ayrıntılı biçimsel modeli Ralf Jung’un A Formal Look at Pinning yazısında açıklanmıştır
?Move tasarımının başarısız olma nedeni
PinöncesindeMoveadlı yeni bir trait’e dayalı tasarım denenmişti- Çoğu tip
Move’u implement eder - Kendine referans içerebilecek tipler
Move’u implement etmez Move’u implement etmeyen bir tipin değerine referans oluşturulduğunda, o değer pinned typestate’e girer ve artık taşınamaz hale gelir
- Çoğu tip
- Bu yaklaşım, referans oluşturma anı ile pinning geçişini ilişkilendirerek güvenliği sağlaması bakımından sezgiseldi
- Hatta gerçekten derleyicinin bir branch’inde implement edilmişti
- Temel sınır, ileride kendine referans verecek bir değere kısa süreliğine referans almak ama henüz pinning yapmak istememek gibi durumların bulunmasıydı
- Örneğin bir değeri kısa süreliğine
Optioniçinde saklayıp sonraOption::takeile çıkarmak isteyebilirsiniz
- Örneğin bir değeri kısa süreliğine
- Daha büyük sorun geriye dönük uyumluluktu
Movebir auto trait yapılamıyordu- Çünkü
mem::swapgibi, mutable reference üzerinden bir değerin her zaman taşınabileceğini varsayan stable API’ler zaten vardı
?Moveolarak ekleme yöntemi de associated type nedeniyle geriye dönük uyumlu değildi- Bir trait’in associated type’ına
?Traitbound’u ekleme yeri trait tanımıdır - Mevcut bir trait’in associated type bound’unu gevşetmek, o bound’a dayanan kodları bozabilir
IntoFuture’ın associated future type’ı,DerefMut’ınTarget’ı, fonksiyon dönüş tipleri, iterator item’ları, index operator dönüş değerleri, aritmetik operator dönüş değerleri gibi pek çok temel işlem associated type’larla iç içedir
- Bir trait’in associated type’ına
- edition ile de kolayca çözülemiyordu
- Çünkü farklı edition’lardaki crate’lerin birlikte kullanılabilmesi için trait arayüzlerinin aynı kalması gerekir
Pin tasarımı
- Nihai tasarım, pinned typestate’i nesne tipinin bir özelliği olarak değil, özel bir işaretçinin oluşturduğu durum olarak ifade eder
Pin, bir işaretçiyi saran wrapper type’tır- built-in reference type’ları da sarabilir
Boxgibi kütüphane tanımlı smart pointer’ları da sarabilir
Pin, ilgili işaretçinin gösterdiği hedefi pinned typestate’e sokar ve hedef artık taşınmamalıdır- Değişikliği en aza indirmek için bu tasarım, derleyici özelliği olarak değil kütüphane API’si olarak implement edilmiştir
- pinned nesneyi gerçekten değiştirmesi gereken kod, unsafe API üzerinden erişmelidir
- Bu sırada, normal mutable reference üzerinden nesnenin taşınmayacağı garantisini sağlamalıdır
- Çoğu tip için pinned durum ile normal durum arasında anlamlı bir fark olmadığından
Unpinauto trait’i eklenmiştir- Bir tip kendine referanslı olamıyorsa, pinned pointer’dan unsafe olmadan mutable reference alınabilir
Unpinimplement eden bir nesneyiPindışına taşımak güvenlidir
- pinning yalnızca pinned pointer’a uygulandığı için, normal unpinned reference’lar
Unpinolmayan tiplerle de çalışmaya devam eder - Ek açıklamalar standart dokümantasyondaki
Pintipi vepinmodülünde yer alır - Bu tasarımın en büyük avantajı, mevcut kodu bozmadan eklenebilmiş olmasıdır
swapgibi referanslanan veriyi taşıyabilen API’ler mutable reference gerektirir- Bir nesneyi
Pinile pinning yaptığınızda, bu tür API’ler artık o nesne üzerinde çağrılamaz - pinned typestate yalnızca özel pinned reference’a uygulandığı için Rust dilinin genel geriye dönük uyumluluk garantisini bozmaz
Pin’in kullanılabilirlik sorunları
Pin, gereksinimleri geriye dönük uyumlu biçimde karşıladı; ancak kullanıcı doğrudan ele almaya başladığı anda bir karmaşıklık uçurumu oluşur- Bir açıklama, pinned nesneyi değiştirmek için unsafe kod gerekmesidir
- Ancak bu sorun abartılmamalıdır
Pin::setile pinned nesneye güvenli biçimde atama yapılabilir- Gerçekte pinned nesneyi değiştirmesi gereken kod çoğunlukla async fonksiyonları Future’a indirgeyen derleyici üretimli koddur; kullanıcıların doğrudan yazması nadirdir
Pin’in koşullu çalıştığı için zor olduğu açıklaması da temel neden değildir- Rust’ta koşullara göre farklı davranırken anlaşılmayı kolaylaştıran özellikler vardır
- non-lexical lifetimes, koşul dallarına göre lifetime’ın farklı noktalarda bitmesini sağlayan bir örnektir
- Temel sorun,
Pin’in saf bir kütüphane tipi olmasına karşılık, normal reference type’ların dilin yerleşik tipleri olması ve çeşitli sözdizimi desteği ile sugar almasıdır- Normal referanslarda doğal biçimde çalışan özellikler pinned reference’larda kaybolur
- Kullanıcının derleyicinin kabul ettiği referans davranışlarına göre kurduğu mental model, pinned reference’larda bozulur
reborrowing ve Pin::as_mut
- Normal mutable reference
&mut T,Copyimplement etmez ama aynı argüman olarak birden çok kez geçirilebilir- Çünkü derleyici,
xyerine&mut *xkoymuş gibi örtük biçimde reborrowing yapar
- Çünkü derleyici,
Pin<&mut T>normal bir kütüphane tipidir veCopyimplement etmediği için bu kolaylık yokturPin<&mut T>iki veya daha fazla kez kullanılırsa move sonrası değer kullanımı hatası ya da daha anlaşılması zor lifetime hataları oluşabilir- reborrow yapmak için açıkça
Pin::as_mutçağrılmalıdır
- Normal mutable reference’larda dereference ve assignment operator ile doğrudan atama yapılabilir;
Pin’de isesetmetodunu öğrenmek gerekir- Bu tür özel API’lerin artmasının nedeni,
Pin’in dil sözdizimi desteği olmayan bir kütüphane tipi olmasıdır
- Bu tür özel API’lerin artmasının nedeni,
pinned projection ve Drop
- pinned projection, bir nesneye ait pinned reference’tan o nesnenin alanına ait pinned reference elde etme problemidir
- projection, nesneden alana erişmek anlamına gelir
- Normal referansta alan erişiminden çok daha zor olduğu için pin-project-lite gibi üçüncü taraf crate’ler kullanılır
- Bu tür crate’ler, macro dahil karmaşık yeni API’lerin öğrenilmesini gerektirir
- En kötü etkileşim pinned projection ile
Droptrait’i arasında ortaya çıkarDrop::dropnormal mutable reference alır- Bir tipte kendine referans veren bir alan varsa ve o alana pin project yapılıp poll edildikten sonra destructor içinde o alan taşınırsa, pinning garantisi bozulabilir
- Örneğin destructor içinde o future stack üzerinde pinning edilip poll edilirse, mevcut pinning garantisi ihlal edilir
pin-project-litegibi crate’ler, destructor tanımlama yeteneğini kısıtlayarak bu sorunu ele alır- Pratikte işe yarar; ancak pinning garantilerini açıklarken dokümante edilmesi gereken karmaşıklık ekler
Drop,Pin’den önce stable olduğu için bir dolambaçlı çözüme ihtiyaç vardı
Mevcut değerlendirme ve sonraki iyileştirme yönü
Pin, rastgele referanslar içeren async fonksiyonların güvenli kendine referans veren nesnelere derlenmesini mümkün kıldı- Referanslar, Rust kullanıcılarının kod yazma biçiminin önemli bir parçası olduğundan, bu olmasaydı async/await’in kullanılabilirliği büyük ölçüde düşerdi
- Aynı zamanda
Pin, mevcut Rust ile tamamen geriye dönük uyumlu bir biçimde eklendi Pin, yüksek performanslı ağ servislerini ve asenkron programlamanın diğer kullanım senaryolarını destekleyen ekosistemin temel yapı taşı haline geldi- Ancak pinned reference’larla çalışmak ordinary reference’larla çalışmaktan çok daha zordur ve
Pingerçekten bir karmaşıklık uçurumu yaratır - Sonraki iyileştirme yönünün temel kavramı pinned places’tır
1 yorum
Hacker News yorumları
Pin’in resmi belgelerde net açıklanmadığı için anlaşılmasının zor olduğunu hep düşünmüşümdür
Özellikle “Pin, bir nesnenin asla taşınmamasını garanti eder” gibi çok açıklama var ama bu doğru değil
Yalnızca nesne
Unpinolmadığında doğru; sıradan nesnelerin çoğuUnpinolduğu için Pin genelde hiçbir şey yapmazBunu anlamam çok uzun sürdü; Pin’in gerçekten anlam taşıdığı
Ttipleri kümesi epey özel ve tuhaf, belgelerin de bunu yeterince vurgulamadığını düşünüyorumElbette pratikte sabitlenmiş durumda ele alınacak tipler olan future’lar ve stream’lerin bu tür özel nesneler olma olasılığı çok daha yüksek
Yine de belgelerin son birkaç yılda çok daha iyi hale geldiğini düşünüyorum
Bu yazıyı yazarken kontrol ettiğimde, oldukça yerinde noktalara odaklandığını görünce şaşırdım; 2019 civarında ise bunun
stdAPI belgelerinden çok Rust referans belgelerine girecek türden sözleşme tanımlarına çok daha fazla kaydığını hatırlıyorumKullanıcıların Pin’i zor bulmasının nedeni bence Pin’in tek başına bir anlamının olmaması
Bu, dildeki diğer sarmalayıcılardan farklı; istisna olarak da asıl kullanım amacıyla neredeyse kimsenin kullanmadığı
AssertUnwindSafesayılabilirElinizde
Pin<&mut InnerType>olduğunda, dilin ya da standart kütüphanenin içindeki Pin’in kendisi, ne yapıp ne yapamayacağınızı söyleyen bir şey sunmazYalnızca
InnerTypeUnpinolarak bildirilmişse, normal bir işaretçiyle yapılabilecek her şeyin yapılabileceği anlamına gelirBunun yerine Pin, “anlamı kendin getir” şeklinde çalışır;
InnerTypesağlayıcısının, sabitlenmiş nesneyi güvenli şekilde manipüle etmek için içerideunsafeolan ek metotlar ve API’ler oluşturduğu bir yapı söz konusudurPin’in kendi amacı,
&mutdeğiştirme,Boxiçinden çıkarıp taşıma gibi daha az içkin yeteneğe sahip bir işaretçi sağlayarak, iç tipin bunun üzerinde ek yetenekleri güvenli biçimde izinli kılabilmesini sağlamaktırİnsanları en çok bu anlam belirsizliğinin kafasını karıştırdığını düşünüyorum; benim de anlamam epey uzun sürdü
Yapısal alanlar ve yapısal olmayan alanlar kavramı ise yalnızca “bu alan sıradan veri, ama şu alan kendi başına sabitlenmek isteyen bir nesne içeriyor” gibi yaygın erişim desenlerini mümkün kılmak için kullanılan bir düzenektir
Unpinuygulamadığı sürece, bu işaretçinin hedefi bir daha taşınamaz demektirDaha kesin ifadeyle, yıkıcısını çalıştırmadan hedefi geçersiz kılamazsınız demektir; taşımanın sorun olmasının nedeni de budur
Belirli haklardan vazgeçtiğinizde, öz-gönderimli değerler saklama hakkı gibi başka haklar elde edersiniz
Bileşenler arası sözleşmeler genelde bu şekilde işler
Benzer biçimde, bir referans üzerinden değiştirme hakkından vazgeçerseniz, aynı anda o referansı alias’lı hale getirebilirsiniz
Bunu her düşündüğümde, konu tamamen farklı ve çok daha ağır olsa da Lincoln filmindeki şu replik aklıma geliyor: “Yasalara itaat edersek, Alex, özgürlüğümüzü kaybetmeye kadar itaat edersek — örneğin baskı kurma özgürlüğünü — daha önce bilmediğimiz başka özgürlükler keşfedebiliriz”
Ancak güvenli kodda bu hakları doğrudan kullanamamanın eğitim açısından sorun olduğu konusunda katılıyorum
Çünkü sabitlenmiş bir referansla ne yapılabileceğini, “derleyicinin oluşturduğu
pollmetodunu çağırmak” dışında kolayca göstermek zorRust ile birkaç yıldır profesyonel olarak geliştirme yapıyorum ama açıkçası Pin’i o kadar iyi anlamıyorum
Teoriyi biliyorum, ama ne zaman kullanılması gerektiğine dair pek sezgim yok
Pin kullanımı fiilen “bir şey denedim, derleyici şikâyet etti; şunu bunu sabitleyince derlendi” durumuna daha yakın
Günlük kodlamada gerçekten oturup derinlemesine anlamam gerekecek kadar bir engel haline henüz gelmedi
unsafeten kaçın ve zeki derleyici insanlarının bunları zaten çözdüğüne şükret” kategorisindeki en yaygın durumlardan biriBuna karşılık C++’ta “bilmiyorum ama mutlaka kullanmam gereken şeyler”in kıyısında sık sık yürürken timsahlar tarafından yenirdim
Öğretirken
UnpinöğelerininPin’den etkilenmediğini netleştirmek için, yerinde tutmak üzere yapılmış bir aracın bile etki etmediği gerçek dünya benzetmeleri kullanmak iyi olabilirCırt cırt kancaları pürüzsüz yüzeye tutunmaz:
Pin→ cırt cırt,Unpin→ pürüzsüz yüzeyMıknatıs manyetik olmayan maddeleri etkilemez:
Pin→ mıknatıs,Unpin→ manyetik olmayan/cam/pirinçYapıştırıcı yapışmaz yüzeye tutunmaz:
Pin→ yapıştırıcı,Unpin→ yapışmaz yüzeyBöylece “cırt cırt”ın nesneyi yerinde sabitlediği, ama nesne “pürüzsüzse” cırt cırt mekanizmasından etkilenmediği netleşir
Rust ekosistemindeki adlandırma havasını düşününce, trait adlarını mıknatıs ve manyetik olmayan gibi bir yöne değiştirmiş olsalardı güzel olurmuş
Unpin, nesnenin her an sabitlenmeye hazır olduğu anlamına gelmiyor mu diye düşünüyorumDün gece yazıyı okudum ama sabitleme için bir düzeltme adımı gerekip gerekmediğini şimdiden unuttum
Bu yüzden
T: Pin + !Unpinyalnızca zımbayla sabitlenebilen bir kâğıt gibi;T: Pin + Unpinise halkası olan bir tabloya, çiviye asılıp halkasını bozmadan tekrar indirilebilen bir şeye daha yakın bence“Değer kimliği” terimi bu yazının hiçbir yerinde tanımlanmıyor ve Mojo belgelerinde de bulamadım; bu yüzden Modular’ın, Mojo’nun Pin’in çözmeye çalıştığı sorunu çözdüğünü hangi temele dayanarak söylediği net değil
Ben de yanıtı bildiğimi iddia etmiyorum ama Chris Lattner ile birlikte Swift’in değer semantiği üzerinde çalışmış Dave Abrahams’ın mükemmel bir sunumu aklıma geliyor
Sunumun başlığı “Value Semantics: Safety, Independence, Projection, & Future of Programming”
[0] https://www.youtube.com/watch?v=QthAU-t3PQ4
Rust, referansları da birinci sınıf tip olarak barındırırken, Swift ve bana göre Mojo referanslara yalnızca parametre geçirme biçimi olarak izin veriyor
Mojo, Swift’in
inoutparametresini genişleterek değişmez referansla geçirme biçimini de eklemiş gibi görünüyorNesnelerin içinde referans saklanmasını engellerseniz Rust’ın derlediği türden kodları uygulayamazsınız; bu yüzden “kendine referans veren struct” sorununu çözmüş olursunuz
Ancak alıntılanan paragrafın Mojo hakkında söylediği şey hiç de bu değil; bu yüzden ne demek istediği epey kafa karıştırıcı
Bana göre sorun, bir değere yönelik
&mutreferansınız varsamem::swap/replacegibi şeylerle o değeri taşıyabilmenizAma pratikte bunu yapmanız gereken durumlar nadirdir
Buna izin verilmeseydi kendine referans veren değerler için
&mutreferans almak tamamen güvenli olurdu gibi geliyorBelki de yalnızca gerektiğinde referans üzerinden taşımayı açıkça seçmenin bir yolu olabilirdi;
swapvereplaceunsafeyapılsaydı bu sorunun tamamından kaçınılabilirdiKeşke biri bu tasarım alanını araştırsa
&mut’un fazla güçlü olduğunu söylemişti&mutiçindeki değeri taşıma yetkisi vermeseydi tüm tasarım çok daha basit olurduBir sonraki yazıda bunu ele alacağım
Rust geriye dönük uyumluluğu korumak zorunda ve
&mutüzerinden değer taşınabileceğine zaten karar verdi; ancak geçmiş kararlara bağlı kalınmasa çok daha temiz bir tasarımın mümkün olduğu kesinmem::swap, değiştirilebilir referans üzerinden değer taşımanın yollarından yalnızca biri; başka pek çok yol da varOption::takebenim oldukça sık kullandığım bir örnek; bunununsafeolması gerçekten tuhaf olurduBu arka plan hikâyesini görmek güzel. WithoutBoats zaten çok yerinde bir zamanda asenkron yineleyiciler,
poll,pinkonularında çok sayıda aktif tartışma yürüttühttps://news.ycombinator.com/from?site=without.boats
Bir dilin ince iç ayrıntılarına bu kadar derinden ve açık biçimde giren çok fazla topluluk yok gibi; izlemesi de çok keyifli
Async hâlâ yarı pişmiş durumda ve çok karmaşık
Bunu son 3 yıldır haftada 40 saat Rust kodu yazan biri olarak söylüyorum
Taşıma kurucusu olan, oluşturulan tüm
Futurealt tiplerinin opak olduğu ve otomatik olarak heap’e ayrıldığı Rust benzeri bir dil hayal edilebilirO zaman kullanıcının onu bozmasının bir yolu olmaz; opak olduğu ve heap’in başka bir yerinde bulunduğu için taşınmasının da yolu olmaz, böylece Pin’e gerek kalmayabilir
Çünkü taşıma kurucusunun olması, taşımanın kavramsal olarak yıkımdan sonra yeniden oluşturma anlamına gelmesidir
Bu sayede, çalıştırmadan önce
Future’ları birleştirip inline hâle getirebilmek gibi iyi bir etkisi varRust’taki değişmezliğe de benzer. Değişmez bellek yoktur; yalnızca değişmez referanslar vardır
Ama bu durumda her asenkron fonksiyon çağrısında ayrı bir ayırma oluşur ve bu bellek yerelliği açısından çok kötüdür
Bir tür sanal stack bundan çok daha iyi olurdu; ancak stack’i varsayılan olarak küçük olacak şekilde optimize etmek için eninde sonunda garbage collection gerekir
Copyile benzer düzeyde dile gömülü birMovetrait’inistd’ye eklemesini gerçekten isterdimMove, bir değeri bellekteki bir adresten başka bir adrese taşıyan fonksiyonu tanımlar veimpl Moveolmayan struct’ların taşınamamasını sağlarNeredeyse tüm tipler
#[derive(Move)]ekler; bu da byte’ları kopyalayan basit bir taşıma fonksiyonunu implement etmekle yetinirAma böyle olursa öz-referanslı tiplerin, future’ların ve daha karmaşık taşıma davranışı gerektiren pek çok şeyin önü açılır
Gerçekte,
CopyileClonearasındaki farkı yansıtacak şekilde bunu iki trait’e ayırmak daha mantıklı olabilirBiri, byte’ları olduğu gibi taşımanın sorun olmadığını derleyiciye bildiren bir marker trait; diğeri de kullanıcı tanımlı “taşıma kurucusu” implementasyonlarına izin veren bir yapı olurdu
Pin’i anlamak çok zor olduğu için Move’un olmasını isterdim
Karmaşık kavramlar çift olumsuzluklarla, bazen de üçlü olumsuzluklarla sarılmış durumda.
fn(...)gibi bir şey görünce “bu ne?” diyorsunuz;unsafepin projection noktasına gelince de kopuyorsunuzNe zaman güvenli, ne zaman güvensiz olduğunu bilmiyorum ve sadece uzak duruyorum
Moveolmayan Rust’tanMoveolan Rust’a geçmek rahatsız edici olurduŞimdiye kadar yazılmış neredeyse tüm struct’lara
#[derive(Move)]eklemek gerekir;stdiçin de aynı şey geçerliEski edition’larda sabitlenmemiş tüm tipler için derleyicinin
Movetrait implementasyonunu çıkarsaması gerekirMekanik olarak mümkün olurdu, sadece çok iş çıkarırdı
Asenkron Rust berbat. Özellikle de neredeyse diğer tüm dillerdeki future/promise yapılarıyla karşılaştırıldığında
Bir gün birileri Rust’ın bellek güvenliği modelini iyileştirip
Movetrait’i ve daha iyi future’ları olan yeni, Rust benzeri bir sistem dili yapacakKişisel olarak Rust’ın makro sistemi yerine derleme zamanı yürütme de olmasını isterdim
Rust’ı seviyorum; ekibin yıllardır yaptığı tüm işleri de seviyorum
Ama gerçekten beklediğim dil, Rust’tan sonra gelecek dil
Aynı fikirleri taşıyan ama Rust’ın hatalarından öğrenmiş bir dil; böyle daha iyi bir Rust tarzı dilin nasıl görüneceği giderek daha netleşiyor
Gerçekten sabırsızlıkla bekliyorum
WithoutBoats’un bir başka harika yazısı
Dürüst olmak gerekirse bu, Rust’ta asenkron runtime’ın içinde soyutlanıp gömülü kaldığı için iyi ki öyle dediğim noktalardan biri
Yine de özel
Futureimplementasyonları dışında Pin’in gerçekte nerede kullanıldığını merak ediyorumİfade biçimine bakınca FFI içinde kullanılabilecekmiş gibi duruyor
Örneğin bir
externfonksiyon*mut Tdöndürüp bir pointer alıyorsa, bunuPin<&mut T>ile sararak daha iyi semantik verilebilirmiş gibi görünüyorAncak yazıda “sabitlenmiş tip durumu hakkındaki bir başka gerçek de, çoğu tip için tamamen alakasız olmasıdır. Bir tipin değeri asla öz-referans içeremiyorsa, onu sabitlemenin faydası yoktur” deniyor
FFI konusunda hâlâ çok acemiyim; bunu güvenli Rust ile sarmalamanın en iyi yolunu anlamak istiyorum
Adrese bağımlı sistem tipleriyle etkileşimde de bu geçerli
Örneğin bazı işletim sistemlerindeki mutex/futex yapılarında kernel dokümantasyonu kullanıcı alanındaki kilit nesnesinin ilklendirildikten sonra adresinin değişmemesi gerektiğini söylediği için, bildiğim kadarıyla
stdPin’e denk bir şey kullanıyorKilitli değilken bile adresin değişmemesi gerektiği nokta ilginç
Genelde koşul yalnızca kilitliyken geçerlidir; bu durumda da referans verilen hedef taşınamayacağı için Pin’e gerek olmaz
Asıl sorun olan iş parçacıklarının verimsizliğini düzeltmemek için muazzam bir iş yapılıyormuş gibi görünüyor
Tüm asenkron kod, istisnasız, durum yönetimi için bolca sözdizimsel şekerle hafif iş parçacıkları uygulayan bir hack’tir
Rust gibi dillerde, aslında hiç gerek olmayacak karmaşıklığı inanılmaz ölçüde artırır
İş parçacıklarının verimlilik ve ölçeklenebilirlik sorunları düzeltilirse bütün bunlar ortadan kalkar
Puf diye yok olur
Java gibi dillerde
nullın “trilyon dolarlık hata” olmasına benzerTek bir tasarım kararı, ya da burada tasarımın yokluğu, muazzam bir karmaşıklık doğuruyor
İptal, ağ uygulamaları ve GUI’lerde çok yararlıdır
İş parçacıkları hem CPU’yu hem ağı yeterince kullanıp ikisinden hiçbirini aşırı işgal etmeyecek şekilde uygulama geliştirmeyi zorlaştırır
İşleri iş parçacığı havuzları arasında devretmeye başladığınızda future’ları yeniden uygulama yoluna girmiş olursunuz
Ya da callback/event’lerle çalışırsınız; bu da kodu parçalara ayırır ve
async/awaittam da bunun sözdizimsel şekeri olmaya çalışıyorduİptal ve timeout’un alternatifi, Go’daki gibi
Contextnesnesini tüm koda örmektir; bu da uçtaki kodunContexte düzgün uymayan fonksiyonları safça çağırması sorununu doğururBu, asenkron kod içindeki asenkron olmayan fonksiyon sorunundan yalnızca çok az daha iyidir
Bu yüzden somut olarak ne yapılması gerektiğini merak ediyorum
Sadece ellerini kaldırıp “Bir gün biri Linux’u düzeltip iş parçacıklarını sihirli biçimde hızlı hale getirebilir, o yüzden dilimize asenkron eklemeyeceğiz” mi demeliydiler?
Ayrıca işletim sistemini tüm asenkron işlerin zamanlayıcısı haline getirirseniz, tüm runtime’lar işletim sistemi zamanlayıcısını kullanmak zorunda kalacağından çeşitli zamanlayıcı tasarımları imkânsız hale gelir
“Tercih edilen yaklaşımın” uygulanamaz olduğu ortaya çıktıktan sonra bile maliyet/fayda değerlendirmesini yeniden yapmıyor gibi görünüyor
“X özelliğini istiyorum, sonuçları umurumda değil” yaklaşımı dil tasarımında pek nadiren kazanan bir hamle olur