JavaScript adalah bahasa yang serbaguna namun fungsional. Variabel, yang merupakan kunci untuk bahasa pemrograman apa pun, dapat digunakan untuk menyimpan nilai yang dapat diakses kapan saja. Namun, saat menggunakan fungsi, ada faktor tertentu yang terkait dengan ruang lingkup fungsi yang membatasi kemampuan kita untuk mengakses variabel. Show
Kami tidak dapat mengakses variabel jika berada di luar cakupan fungsi, sehingga variabel yang ingin kami gunakan harus memiliki cakupan yang tepat saat dideklarasikan. Untuk menghindari masalah yang terkait dengan ruang lingkup, penting untuk memahami variabel global. Oleh karena itu, dalam artikel ini, kita akan membahas variabel dan ruang lingkup global. NS cakupan dari suatu fungsi dapat dianggap sebagai batas di mana fungsi tersebut dapat diakses. Namun, sementara suatu fungsi tidak tahu apa yang terjadi di luar kurung kurawal yang mendefinisikannya, a variabel global dapat diakses dari mana saja dalam program. SintaksisSintaks yang digunakan untuk membuat variabel global, yang ditunjukkan di bawah, tidak berbeda dengan yang digunakan untuk membuat variabel lain. var nama variabel = nilai Namun, lokasi deklarasi ini sangat penting. Kami akan mengeksplorasi konsep ini lebih lengkap dengan mempertimbangkan beberapa contoh. ContohPertama, mari kita buat fungsi yang disebut pengurangan. fungsi
pengurangan(Sebuah,B){ Dalam fungsi ini, kami menginisialisasi variabel dan memberinya nilai. Sekarang, kita dapat mencoba mengakses variabel di fungsi lain, yaitu pembagian, dan memanggil fungsi itu. fungsi divisi(Sebuah,B){ Namun, kami mendapatkan kesalahan referensi berikut karena variabel subNama tidak didefinisikan dalam lingkup yang benar. Kesalahan ini akan terjadi setiap kali kami mencoba mengakses subNum di luar fungsi yang didefinisikan. Sebagai contoh: fungsi pengurangan(Sebuah,B){ Di sini, kita masih tidak dapat mengakses variabel karena dibatasi pada fungsi pengurangan. Namun, mari kita lihat apa yang terjadi jika kita membuat variabel di luar fungsi—misalnya, di awal skrip: var globalVar =11; Sekarang, mari kita coba mengaksesnya: menghibur.catatan(globalVar); Seperti yang ditunjukkan di bawah ini, kami tidak lagi mendapatkan kesalahan referensi. Lebih-lebih lagi, globalVar harus dapat diakses dari fungsi apa pun. fungsi
divisi(Sebuah,B){ Seperti yang Anda lihat di bawah, globalVar masih dapat diakses. KesimpulanPada artikel ini, kami menjelaskan ruang lingkup dan variabel global dengan menggunakan contoh sederhana. Kami harap Anda terus belajar JavaScript dengan linuxhint.com. Declaring and using global variables in Rust can be tricky. Typically for this language, Rust ensures robustness by forcing us to be very explicit. In this article, I’ll discuss the pitfalls the Rust compiler wants to save us from. Then I’ll show you the best solutions available for different scenarios. OverviewThere are many options for implementing global state in Rust. If you’re in a hurry, here’s a quick overview of my recommendations. You can jump to specific sections of this article via the following links:
A Naive First Attempt at Using Global Variables in RustLet’s start with an example of how not to use global variables. Assume I want to store the starting time of the program in a global string. Later, I want to access the value from multiple threads. A Rust beginner might be tempted to declare a global variable exactly like any other variable in Rust, using
Try it for yourself on the playground! This is invalid syntax for Rust. The The reasoning behind this is that Global variables are stored in the data segment of the program. They have a fixed address that doesn’t change during execution. Therefore, the code segment can include constant addresses and requires no space on the stack at all. Okay, so we can understand why we need a different syntax. Rust, as a modern systems programming language, wants to be very explicit about memory management. Let’s try again with
The compiler isn’t happy, yet:
Hm, so the initialization value of a static variable can’t be computed at runtime. Then maybe just let it be uninitialized?
This yields a new error:
So that doesn’t work either! All static values must be fully initialized and valid before any user code runs. If you’re coming over to Rust from another language, such as JavaScript or Python, this might seem unnecessarily restrictive. But any C++ guru can tell you stories about the static initialization order fiasco, which can lead to an undefined initialization order if we’re not careful. For example, imagine something like this:
In this code snippet, there’s no safe initialization order, due to circular dependencies. If it were C++, which doesn’t care about safety, the result would be At least it’s defined what the result is. However, the “fiasco” starts when the static variables are from different In Rust, zero-initializing is not a thing. After all, zero is an invalid value for many types, such as But can I circumvent initialization by
using
Ah, well, the error we get is…
At this point, I could wrap it in an Refactor the ExampleYou may already have noticed that this example doesn’t require global variables at all. And more often than not, if we can think of a solution without global variables, we should avoid them. The idea here is to put the declaration inside the main function:
The only problem is the borrow-checker:
This error is not exactly obvious. The compiler is
telling us that the spawned thread may live longer than the value Technically, we can see that this is impossible. The threads are joined, thus the main thread won’t exit before the child threads have finished. But the compiler isn’t smart enough to figure out this particular case. In general, when a new thread is spawned, the provided closure can only borrow items with a static lifetime. In other words, the borrowed values must be alive for the full program lifetime. For anyone just learning about Rust, this could be the point where you want to reach out to global variables. But there are at least two solutions that are much easier than that. The simplest is to clone the string value and then move ownership of the strings into the closures. Of course, that requires an extra allocation and some extra memory. But in this case, it’s just a short string and nothing performance-critical. But what if it was a much larger object to share? If you don’t want to clone it, wrap it behind a reference-counted smart pointer. Rc is the single-threaded reference-counted type. Arc is the atomic version that can safely share values between threads. So, to satisfy the compiler, we can use
Try it for yourself on the playground! This has been a quick rundown on how to share state between threads while avoiding global variables. Beyond what I’ve shown you so far, you might also need interior mutability to modify the shared state. Full coverage of interior mutability is
outside the scope of this article. But in this particular example, I would pick When the Global Variable Value Is Known at Compile TimeIn my experience, the most common use cases for global state are not variables but constants. In Rust, they come in two flavors:
Both of them can be initialized with compile-time constants. These could be simple values, such as
Usually, Should you require interior mutability, there are several options. For most primitives, there’s a corresponding atomic variant available in std::sync::atomic. They provide a clean API to load, store, and update values atomically. In the absence of atomics, the usual choice is a lock. Rust’s standard library offers a read-write lock ( However,
if you need to calculate the value at runtime, or need heap-allocation, then Single-threaded Global Variables in Rust with Runtime InitializationMost applications I write only have a single thread. In that case, a locking mechanism isn’t necessary. However, we shouldn’t use For example, borrowing unsafely from the global variable could give us multiple mutable references simultaneously. Then we could use one of them to iterate over a vector and another to remove values from the same vector. The iterator could then go beyond the valid memory boundary, a potential crash that safe Rust would have prevented. But the standard library has a way to “globally” store values for safe access within a single thread. I’m talking about thread locals. In the presence of many threads, each thread gets an independent copy of the variable. But in our case, with a single thread, there’s only one copy. Thread locals are created with the
It’s not the simplest of all solutions. But it allows us to perform arbitrary initialization code, which will run just in time when the first access to the value occurs. Thread-locals are really good when it comes to interior mutability. Unlike all the other solutions, it doesn’t require Sync. This allows using RefCell for interior mutability, which avoids the locking overhead of Mutex. The absolute performance of thread-locals is highly dependent on the platform. But I did some quick tests on my own PC comparing it to interior mutability relying on locks and found it to be 10x faster. I don’t expect the result to be flipped on any platform, but make sure to run your own benchmarks if you deeply care about performance. Here’s an example of how to use
Try it for yourself on the playground! As a side note, even though threads in WebAssembly are different from threads on an x86_64 platform, this pattern with One caveat about thread-locals is that their implementation depends on the platform. Usually, this is nothing you’d notice, but be aware that the drop-semantics are platform-dependent because of that. All that said, the solutions for multi-threaded globals obviously also work for the single-threaded cases. And without interior mutability, those seem to be just as fast as thread-locals. So let’s have a look at that next. Multi-threaded Global Variables with Runtime InitializationThe standard library currently has no great solution for safe global variables with runtime initialization. However, using std::sync::Once, it’s possible to build something that uses The example in the official documentation is a good starting point. Should you also need interior mutability, you’d have to combine that approach with a read-write lock or a mutex. Here’s how that might look:
Try it for yourself on the playground! If you’re looking for something simpler, I can highly recommend one of two crates, which I’ll discuss in the next section. External Libraries for Managing Global Variables in RustBased on popularity and personal taste, I want to recommend two libraries that I think are the best choice for easy global variables in Rust, as of 2021. Once Cell is currently considered for the standard library. (See this tracking issue.) If
you’re on a nightly compiler, you can already use the unstable API for it by adding Here’s an example using
Try it for yourself on the playground! Finally, there’s also Lazy
Static, currently the most popular crate for initialization of global variables. It uses a macro with a small syntax extension ( Here’s the same example again, translated from
Try it for yourself on the playground! The decision between ConclusionThese have been all the (sensible) ways to implement global variables in Rust that I’m aware of. I wish it were simpler. But global state is inherently complex. In combination with Rust’s memory safety guarantees, a simple catch-them-all solution seems to be impossible. But I hope this write-up has helped you to see through the plethora of available options. In general, the Rust community tends to give maximum power to the user — which makes things more complicated as a side-effect. It can be hard to keep track of all the details. As a result, I spend a lot of my free time playing around with Rust features to explore the possibilities. In the process, I usually implement smaller or larger hobby projects — such as video games — and upload them to my GitHub profile. Then, if I find something interesting in my experimentation with the language, I write about it on my private blog. Check that out if you’d like to read more in-depth Rust content! |