Sözlükler
Cairo, çekirdek kütüphanesinde bir sözlük benzeri tür sunar. Felt252Dict<T>
veri türü, her bir anahtarın benzersiz olduğu ve karşılık gelen bir değerle ilişkilendirildiği anahtar-değer çiftlerinin bir koleksiyonunu temsil eder. Bu tür veri yapıları, farklı programlama dillerinde haritalar, karma tablolar, "ilişkisel diziler ve daha birçok farklı isimle bilinir.
Felt252Dict<T>
türü, verilerinizi belirli bir şekilde düzenlemek istediğinizde ve bir Array<T>
kullanmanın ve indekslemenin yetersiz kaldığı durumlar için faydalıdır. Cairo sözlükleri, ayrıca programcıya, mevcut olmayan değiştirilebilir belleğin varlığını kolayca simüle etme imkanı da tanır.
Sözlüklerin Temel Kullanımı
Diğer dillerde yeni bir sözlük oluştururken genellikle anahtar ve değerin veri türlerini tanımlamak normaldir. Cairo'da, anahtar türü felt252
ile sınırlıdır, bu da sadece değer veri türünü belirtme olasılığını bırakır, Felt252Dict<T>
'de bu T
ile temsil edilir.
Felt252Dict<T>
'nin temel işlevselliği, tüm temel işlemleri içeren Felt252DictTrait
trait'inde uygulanmıştır. Bunlar arasında şunları bulabiliriz:
insert(felt252, T) -> ()
bir sözlük örneğine değer yazmak için veget(felt252) -> T
içinden değerleri okumak için.
Bu fonksiyonlar, diğer dillerdeki gibi sözlükleri manipüle etmemize olanak tanır. Aşağıdaki örnekte, bireyler ve bakiyeleri arasındaki eşlemeyi temsil eden bir sözlük oluşturuyoruz:
use core::dict::Felt252Dict;
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
balances.insert('Alex', 100);
balances.insert('Maria', 200);
let alex_balance = balances.get('Alex');
assert!(alex_balance == 100, "Balance is not 100");
let maria_balance = balances.get('Maria');
assert!(maria_balance == 200, "Balance is not 200");
}
Felt252Dict<u64>
'ün yeni bir örneğini, Default
trait'inin default
metodu kullanılarak oluşturabilir ve insert
metodu kullanarak her biri kendi bakiyesine sahip iki birey ekleyebiliriz. Son olarak, get
metodu ile kullanıcılarımızın bakiyelerini kontrol ederiz. Bu metodlar çekirdek kütüphanedeki Felt252DictTrait
trait'inde tanımlanmıştır.
Throughout the book we have talked about how Cairo's memory is immutable, meaning you can only write to a memory cell once but the Felt252Dict<T>
type represents a way to overcome this obstacle. We will explain how this is implemented later on in "Dictionaries Underneath".
Önceki örneğimizi temel alarak, aynı kullanıcının bakiyesinin nasıl değiştiğini gösteren bir kod örneği sunalım:
use core::dict::Felt252Dict;
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
// Insert Alex with 100 balance
balances.insert('Alex', 100);
// Check that Alex has indeed 100 associated with him
let alex_balance = balances.get('Alex');
assert!(alex_balance == 100, "Alex balance is not 100");
// Insert Alex again, this time with 200 balance
balances.insert('Alex', 200);
// Check the new balance is correct
let alex_balance_2 = balances.get('Alex');
assert!(alex_balance_2 == 200, "Alex balance is not 200");
}
Bu örnekte 'Alex' kişisini iki kez eklediğimize dikkat edin, her seferinde farklı bir bakiye kullanıldı ve bakiyesi kontrol edildiğinde her zaman son eklenen değeri aldı! Felt252Dict<T>
bize verilen herhangi bir anahtar için saklanan değeri "yeniden yazma" imkanı sağlar.
Sözlüklerin nasıl uygulandığını açıklamadan önce, bir Felt252Dict<T>
örneği oluşturduğunuzda, perde arkasında tüm anahtarların ilişkili değerlerinin sıfır olarak başlatıldığını belirtmek önemlidir. Bu, örneğin, mevcut olmayan bir kullanıcının bakiyesini almaya çalıştığınızda bir hata veya tanımsız bir değer yerine 0 alacağınız anlamına gelir. Bu aynı zamanda bir sözlükten veri silmenin mümkün olmadığı anlamına gelir. Bu yapının kodunuza dahil ederken göz önünde bulundurulması gereken bir noktadır.
Bu noktaya kadar, Felt252Dict<T>
'nin tüm temel özelliklerini ve diğer dillerdeki karşılık gelen veri yapılarının aynı davranışını nasıl taklit ettiğini gördük, tabii ki dışarıdan. Cairo, temelde belirsiz bir Turing-tam programlama dilidir, var olan diğer popüler dillerden çok farklıdır, bu da sözlüklerin de çok farklı şekilde uygulandığı anlamına gelir!
Takip eden bölümlerde, Felt252Dict<T>
iç mekanizmaları ve onları çalışır hale getirmek için yapılan kompromisler hakkında bazı içgörüler sunacağız. Bundan sonra, sözlükleri diğer veri yapılarıyla nasıl kullanacağımıza ve onlarla etkileşimde bulunmanın başka bir yolu olarak entry
metodunu nasıl kullanacağımıza bakacağız.
Sözlükler Altında
Cairo'nun belirsiz tasarımının kısıtlamalarından biri, bellek sisteminin değişmez olmasıdır, bu nedenle değiştirilebilirliği simüle etmek için dil, Felt252Dict<T>
'yi girişlerin bir listesi olarak uygular. Girişlerin her biri, bir sözlüğün okuma/güncelleme/yazma amaçları için erişildiği bir zamanı temsil eder. Bir girişin üç alanı vardır:
- Bir sözlükteki bu anahtar-değer çifti için anahtarı tanımlayan bir
key
alanı. key
'de hangi önceki değerin tutulduğunu belirten birprevious_value
alanı.key
'de tutulan yeni değeri belirten birnew_value
alanı.
Eğer Felt252Dict<T>
'yi yüksek seviye yapılar kullanarak uygulamaya çalışırsak, içsel olarak onu Array<Entry<T>>
olarak tanımlarız, burada her Entry<T>
, temsil ettiği anahtar-değer çifti hakkında bilgi ve tuttuğu önceki ve yeni değerleri içerir. Entry<T>
'nin tanımı şu şekilde olurdu:
struct Entry<T> {
key: felt252,
previous_value: T,
new_value: T,
}
Bir Felt252Dict<T>
ile her etkileşimde, yeni bir Entry<T>
kaydedilecektir:
get
, durumda bir değişiklik olmadığı ve önceki ile yeni değerlerin aynı değerle kaydedildiği bir giriş kaydeder.insert
,new_value
'nun eklenen eleman veprevious_value
'nun bu öncesi eklenen son eleman olduğu yeni birEntry<T>
kaydeder. Belirli bir anahtar için bu ilk giriş ise, önceki değer sıfır olacaktır.
Bu giriş listesinin kullanımı, herhangi bir yeniden yazımın olmadığını, sadece Felt252Dict<T>
etkileşimi başına yeni bellek hücrelerinin oluşturulduğunu gösterir. Bunu, önceki bölümden balances
sözlüğünü kullanarak 'Alex' ve 'Maria' kullanıcılarını ekleyerek gösterelim:
use core::dict::Felt252Dict;
struct Entry<T> {
key: felt252,
previous_value: T,
new_value: T,
}
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
balances.insert('Alex', 100_u64);
balances.insert('Maria', 50_u64);
balances.insert('Alex', 200_u64);
balances.get('Maria');
}
Bu talimatlar daha sonra aşağıdaki giriş listesini üretecektir:
key | previous | new |
---|---|---|
Alex | 0 | 100 |
Maria | 0 | 50 |
Alex | 100 | 200 |
Maria | 50 | 50 |
'Alex' iki kez eklendiğinden, iki kez görünür ve previous
ile current
değerleri doğru şekilde ayarlanmıştır. Ayrıca, 'Maria'dan okuma, önceki ile mevcut değerler arasında bir değişiklik olmadan bir giriş kaydetti.
Felt252Dict<T>
'yi bu şekilde uygulamanın anlamı, her okuma/yazma işlemi için, aynı key
ile son girişi aramak üzere tüm giriş listesinin taranmasıdır. Giriş bulunduğunda, new_value
'si çıkarılır ve yeni eklenen girişte previous_value
olarak kullanılır. Bu,Felt252Dict<T>
ile etkileşimin en kötü durum zaman karmaşıklığının O(n)
olduğu anlamına geliyor, burada n
liste içindeki girişlerin sayısıdır.
Felt252Dict<T>
'yi uygulamanın alternatif yollarını düşünürseniz, muhtemelen previous_value
alanının tamamen gereksiz olduğu yöntemler bulabilirsiniz, yine de, Cairo normal bir dil olmadığı için bu işe yaramaz. Cairo'nun amaçlarından biri, STARK ispat sistemi ile, hesaplama bütünlüğünün kanıtlarını üretmektir. Bu, programın doğru ve Cairo kısıtlamaları içinde çalıştığını doğrulamanız gerektiği anlamına gelir. Bu sınır kontrollerinden biri "sözlük sıkıştırması"dır ve bu, her giriş için önceki ve yeni değerler hakkında bilgi gerektirir.
Sözlükleri Ezmek
Felt252Dict<T>
kullanan bir Cairo programının yürütmesiyle üretilen kanıtın doğru olduğunu doğrulamak için, sözlükle herhangi bir yasa dışı oynama yapılmadığını kontrol etmemiz gerekir. Bu, her girişi gözden geçiren ve yürütme boyunca sözlüğe erişimin tutarlı kaldığını kontrol eden squash_dict
adlı bir yöntem aracılığıyla yapılır.
Sıkıştırma işlemi şu şekildedir: belirli bir anahtar k
ile tüm girişler, eklenme sıraları göz önünde bulundurularak alınır, i'nci girişin new_value
'si i+1'inci girişin previous_value
ile eşit olduğu doğrulanır.
Örneğin, aşağıdaki giriş listesi göz önüne alındığında:
key | previous | new |
---|---|---|
Alex | 0 | 150 |
Maria | 0 | 100 |
Charles | 0 | 70 |
Maria | 100 | 250 |
Alex | 150 | 40 |
Alex | 40 | 300 |
Maria | 250 | 190 |
Alex | 300 | 90 |
Ezme işleminden sonra giriş listesi şu şekle indirgenecektir:
key | previous | new |
---|---|---|
Alex | 0 | 90 |
Maria | 0 | 190 |
Charles | 0 | 70 |
İlk tablonun herhangi bir değerinde bir değişiklik olması durumunda, ezme işlemi çalışma sırasında başarısız olurdu.
Sözlük İmhası
If you run the examples from "Basic Use of Dictionaries" section, you'd notice that there was never a call to squash dictionary, but the program compiled successfully nonetheless. What happened behind the scene was that squash was called automatically via the Felt252Dict<T>
implementation of the Destruct<T>
trait. This call occurred just before the balance
dictionary went out of scope.
The Destruct<T>
trait represents another way of removing instances out of scope apart from Drop<T>
. The main difference between these two is that Drop<T>
is treated as a no-op operation, meaning it does not generate new CASM while Destruct<T>
does not have this restriction. The only type which actively uses the Destruct<T>
trait is Felt252Dict<T>
, for every other type Destruct<T>
and Drop<T>
are synonyms. You can read more about these traits in Drop and Destruct section of Appendix C.
Later in "Dictionaries as Struct Members" section, we will have a hands-on example where we implement the Destruct<T>
trait for a custom type.
Daha Fazla Sözlük
Bu noktaya kadar, Felt252Dict<T>
'nin işlevselliğine ve belirli bir şekilde nasıl ve neden uygulandığına dair kapsamlı bir genel bakış verdik. Hepsini anlamadıysanız endişelenmeyin, çünkü bu bölümde sözlükleri kullanarak daha fazla örneğe sahip olacağız.
We will start by explaining the entry
method which is part of a dictionary basic functionality included in Felt252DictTrait<T>
which we didn't mention at the beginning. Soon after, we will see examples of how to use Felt252Dict<T>
with other complex types such as Array<T>
.
Giriş ve Sonuçlandırma
In the "Dictionaries Underneath" section, we explained how Felt252Dict<T>
internally worked. It was a list of entries for each time the dictionary was accessed in any manner. It would first find the last entry given a certain key
and then update it accordingly to whatever operation it was executing. The Cairo language gives us the tools to replicate this ourselves through the entry
and finalize
methods.
entry
metodu, belirli bir anahtar için yeni bir giriş oluşturma amacıyla Felt252DictTrait<T>
ile gelir. Çağrıldığında, bu metod sözlüğün sahipliğini alır ve güncellenecek girişi döndürür. Metod imzası şöyle:
fn entry(self: Felt252Dict<T>, key: felt252) -> (Felt252DictEntry<T>, T) nopanic
İlk giriş parametresi, sözlüğün sahipliğini alırken, ikincisi uygun girişi oluşturmak için kullanılır. Bir Felt252DictEntry<T>
, Cairo'nun sözlük girişlerini temsil etmek için kullandığı tür, ve önceden tutulan bir T
değeri içeren bir demet döndürür. nopanic
notasyonu, fonksiyonun asla panik olmayacağının garantilendiğini belirtir.
Bir sonraki adım, girişi yeni değerle güncellemektir. Bunun için, girişi ekleyen ve sözlüğün sahipliğini geri döndüren finalize
metodunu kullanırız:
fn finalize(self: Felt252DictEntry<T>, new_value: T) -> Felt252Dict<T>
Bu metod, girişi ve yeni değeri parametre olarak alır ve güncellenmiş sözlüğü döndürür.
entry
ve finalize
kullanarak bir örneği inceleyelim. Sözlükten get
metodunun kendi versiyonumuzu uygulamak istediğimizi hayal edelim. O zaman "şunu yapmalıyız:
entry
metodu kullanarak eklemek için yeni girişi oluşturun.new_value
previous_value
'a eşit olduğu yerde girişi geri ekleyin.- Değeri döndür.
Özel get uygulamamız şu şekilde görünecektir:
use core::dict::{Felt252Dict, Felt252DictEntryTrait};
fn custom_get<T, +Felt252DictValue<T>, +Drop<T>, +Copy<T>>(
ref dict: Felt252Dict<T>, key: felt252,
) -> T {
// Get the new entry and the previous value held at `key`
let (entry, prev_value) = dict.entry(key);
// Store the value to return
let return_value = prev_value;
// Update the entry with `prev_value` and get back ownership of the dictionary
dict = entry.finalize(prev_value);
// Return the read value
return_value
}
The ref
keyword means that the ownership of the variable will be given back at the end of the function. This concept will be explained in more detail in the "References and Snapshots" section.
insert
metodunu uygulamak benzer bir iş akışını takip eder, sadece sonlandırırken yeni bir değer eklenir. Uygulayacak olsaydık, şöyle görünecekti:
use core::dict::{Felt252Dict, Felt252DictEntryTrait};
fn custom_insert<T, +Felt252DictValue<T>, +Destruct<T>, +Drop<T>>(
ref dict: Felt252Dict<T>, key: felt252, value: T,
) {
// Get the last entry associated with `key`
// Notice that if `key` does not exist, `_prev_value` will
// be the default value of T.
let (entry, _prev_value) = dict.entry(key);
// Insert `entry` back in the dictionary with the updated value,
// and receive ownership of the dictionary
dict = entry.finalize(value);
}
Son bir not olarak, bu iki metod, Felt252Dict<T>
için insert
ve get
nasıl uygulandıysa benzer bir şekilde uygulanır. Bu kod, bazı örnek kullanımları gösterir:
use core::dict::{Felt252Dict, Felt252DictEntryTrait};
fn custom_get<T, +Felt252DictValue<T>, +Drop<T>, +Copy<T>>(
ref dict: Felt252Dict<T>, key: felt252,
) -> T {
// Get the new entry and the previous value held at `key`
let (entry, prev_value) = dict.entry(key);
// Store the value to return
let return_value = prev_value;
// Update the entry with `prev_value` and get back ownership of the dictionary
dict = entry.finalize(prev_value);
// Return the read value
return_value
}
fn custom_insert<T, +Felt252DictValue<T>, +Destruct<T>, +Drop<T>>(
ref dict: Felt252Dict<T>, key: felt252, value: T,
) {
// Get the last entry associated with `key`
// Notice that if `key` does not exist, `_prev_value` will
// be the default value of T.
let (entry, _prev_value) = dict.entry(key);
// Insert `entry` back in the dictionary with the updated value,
// and receive ownership of the dictionary
dict = entry.finalize(value);
}
fn main() {
let mut dict: Felt252Dict<u64> = Default::default();
custom_insert(ref dict, '0', 100);
let val = custom_get(ref dict, '0');
assert!(val == 100, "Expecting 100");
}
Dictionaries of Types not Supported Natively
One restriction of Felt252Dict<T>
that we haven't talked about is the trait Felt252DictValue<T>
. This trait defines the zero_default
method which is the one that gets called when a value does not exist in the dictionary. This is implemented by some common data types, such as most unsigned integers, bool
and felt252
- but it is not implemented for more complex types such as arrays, structs (including u256
), and other types from the core library. This means that making a dictionary of types not natively supported is not a straightforward task, because you would need to write a couple of trait implementations in order to make the data type a valid dictionary value type. To compensate this, you can wrap your type inside a Nullable<T>
.
Nullable<T>
is a smart pointer type that can either point to a value or be null
in the absence of value. It is usually used in Object Oriented Programming Languages when a reference doesn't point anywhere. The difference with Option
is that the wrapped value is stored inside a Box<T>
data type. The Box<T>
type is a smart pointer that allows us to use a dedicated boxed_segment
memory segment for our data, and access this segment using a pointer that can only be manipulated in one place at a time. See Smart Pointers Chapter for more information.
Bir örnekle kullanımını gösterelim. Bir sözlük içinde Span<felt252>
saklamayı deneyeceğiz. Bunun için Nullable<T>
ve Box<T>
kullanacağız. Ayrıca, Array<T>
yerine bir Span<T>
saklıyoruz çünkü sonuncusu, bir sözlükten okuma için gerekli olan Copy<T>
trait'ini uygulamaz.
use core::dict::Felt252Dict;
use core::nullable::{NullableTrait, match_nullable, FromNullableResult};
fn main() {
// Create the dictionary
let mut d: Felt252Dict<Nullable<Span<felt252>>> = Default::default();
// Create the array to insert
let a = array![8, 9, 10];
// Insert it as a `Span`
d.insert(0, NullableTrait::new(a.span()));
//...
Bu kod parçacığında, ilk olarak d
adında yeni bir sözlük oluşturduk. Onun Nullable<Span>
tutmasını istiyoruz. Bunun ardından, bir dizi oluşturduk ve değerlerle doldurduk.
The last step is inserting the array as a span inside the dictionary. Notice that we do this using the new
function of the NullableTrait
.
Eleman bir kez sözlük içine girdiğinde ve onu almak istediğimizde, aynı adımları ters sırada takip ediyoruz. Aşağıdaki kod, bunun nasıl yapılabileceğini gösterir:
//...
// Get value back
let val = d.get(0);
// Search the value and assert it is not null
let span = match match_nullable(val) {
FromNullableResult::Null => panic!("No value found"),
FromNullableResult::NotNull(val) => val.unbox(),
};
// Verify we are having the right values
assert!(*span.at(0) == 8, "Expecting 8");
assert!(*span.at(1) == 9, "Expecting 9");
assert!(*span.at(2) == 10, "Expecting 10");
}
İşte geldik:
get
kullanarak değeri okuyun.match_nullable
işlevini kullanarak null olmadığını doğrulayın.- Kutunun içindeki değeri açtı ve doğru olduğunu onayladı.
Senaryonun tamamı şu şekilde görünecektir:
use core::dict::Felt252Dict;
use core::nullable::{NullableTrait, match_nullable, FromNullableResult};
fn main() {
// Create the dictionary
let mut d: Felt252Dict<Nullable<Span<felt252>>> = Default::default();
// Create the array to insert
let a = array![8, 9, 10];
// Insert it as a `Span`
d.insert(0, NullableTrait::new(a.span()));
// Get value back
let val = d.get(0);
// Search the value and assert it is not null
let span = match match_nullable(val) {
FromNullableResult::Null => panic!("No value found"),
FromNullableResult::NotNull(val) => val.unbox(),
};
// Verify we are having the right values
assert!(*span.at(0) == 8, "Expecting 8");
assert!(*span.at(1) == 9, "Expecting 9");
assert!(*span.at(2) == 10, "Expecting 10");
}
Using Arrays inside Dictionaries
In the previous section, we explored how to store and retrieve complex types inside a dictionary using Nullable<T>
and Box<T>
. Now, let's take a look at how to store an array inside a dictionary and dynamically modify its contents.
Storing arrays in dictionaries in Cairo is slightly different from storing other types. This is because arrays are more complex data structures that require special handling to avoid issues with memory copying and references.
First, let's look at how to create a dictionary and insert an array into it. This process is pretty straightforward and follows a similar pattern to inserting other types of data:
use core::dict::Felt252Dict;
fn main() {
let arr = array![20, 19, 26];
let mut dict: Felt252Dict<Nullable<Array<u8>>> = Default::default();
dict.insert(0, NullableTrait::new(arr));
println!("Array inserted successfully.");
}
However, attempting to read an array from the dictionary using the get
method will result in a compiler error. This is because get
tries to copy the array in memory, which is not possible for arrays (as we've already mentioned in the previous section, Array<T>
does not implement the Copy<T>
trait):
use core::nullable::{match_nullable, FromNullableResult};
use core::dict::Felt252Dict;
fn main() {
let arr = array![20, 19, 26];
let mut dict: Felt252Dict<Nullable<Array<u8>>> = Default::default();
dict.insert(0, NullableTrait::new(arr));
println!("Array: {:?}", get_array_entry(ref dict, 0));
}
fn get_array_entry(ref dict: Felt252Dict<Nullable<Array<u8>>>, index: felt252) -> Span<u8> {
let val = dict.get(0); // This will cause a compiler error
let arr = match match_nullable(val) {
FromNullableResult::Null => panic!("No value!"),
FromNullableResult::NotNull(val) => val.unbox(),
};
arr.span()
}
$ scarb cairo-run
Compiling no_listing_15_dict_of_array_attempt_get v0.1.0 (listings/ch03-common-collections/no_listing_15_dict_of_array_attempt_get/Scarb.toml)
error: Trait has no implementation in context: core::traits::Copy::<core::nullable::Nullable::<core::array::Array::<core::integer::u8>>>.
--> listings/ch03-common-collections/no_listing_15_dict_of_array_attempt_get/src/lib.cairo:13:20
let val = dict.get(0); // This will cause a compiler error
^*^
error: could not compile `no_listing_15_dict_of_array_attempt_get` due to previous error
error: `scarb metadata` exited with error
To correctly read an array from the dictionary, we need to use dictionary entries. This allows us to get a reference to the array value without copying it:
fn get_array_entry(ref dict: Felt252Dict<Nullable<Array<u8>>>, index: felt252) -> Span<u8> {
let (entry, _arr) = dict.entry(index);
let mut arr = _arr.deref_or(array![]);
let span = arr.span();
dict = entry.finalize(NullableTrait::new(arr));
span
}
Note: We must convert the array to a
Span
before finalizing the entry, because callingNullableTrait::new(arr)
moves the array, thus making it impossible to return it from the function.
To modify the stored array, such as appending a new value, we can use a similar approach. The following append_value
function demonstrates this:
fn append_value(ref dict: Felt252Dict<Nullable<Array<u8>>>, index: felt252, value: u8) {
let (entry, arr) = dict.entry(index);
let mut unboxed_val = arr.deref_or(array![]);
unboxed_val.append(value);
dict = entry.finalize(NullableTrait::new(unboxed_val));
}
In the append_value
function, we access the dictionary entry, dereference the array, append the new value, and finalize the entry with the updated array.
Note: Removing an item from a stored array can be implemented in a similar manner.
Below is the complete example demonstrating the creation, insertion, reading, and modification of an array in a dictionary:
use core::nullable::NullableTrait;
use core::dict::{Felt252Dict, Felt252DictEntryTrait};
fn append_value(ref dict: Felt252Dict<Nullable<Array<u8>>>, index: felt252, value: u8) {
let (entry, arr) = dict.entry(index);
let mut unboxed_val = arr.deref_or(array![]);
unboxed_val.append(value);
dict = entry.finalize(NullableTrait::new(unboxed_val));
}
fn get_array_entry(ref dict: Felt252Dict<Nullable<Array<u8>>>, index: felt252) -> Span<u8> {
let (entry, _arr) = dict.entry(index);
let mut arr = _arr.deref_or(array![]);
let span = arr.span();
dict = entry.finalize(NullableTrait::new(arr));
span
}
fn main() {
let arr = array![20, 19, 26];
let mut dict: Felt252Dict<Nullable<Array<u8>>> = Default::default();
dict.insert(0, NullableTrait::new(arr));
println!("Before insertion: {:?}", get_array_entry(ref dict, 0));
append_value(ref dict, 0, 30);
println!("After insertion: {:?}", get_array_entry(ref dict, 0));
}