You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rust 起步的目标是创建一个安全且合用的系统编程语言。为了追求这个目标,它探索了很多想法,其中一些(生命周期、traits)被保留,而其他则被丢弃(类型体系系统、绿色线程)。 Also, in the run up to 1.0 a lot of the standard library was rewritten as early designs were updated to best use Rust's features and provide quality, consistent cross-platform APIs. 现在的 Rust 已达到了 1.0,语言保证为 “稳定”(stable);虽然它可能继续演变,但对于目前的 Rust 来说,代码在未来的发行版本上能继续工作。
“特性门控”(Feature Gates)是 Rust 用来稳定编译器、语言和标准库特性的机制。 A feature that is "gated" is accessible only on the nightly release channel, and then only when it has been explicitly enabled through #[feature] attributes or the -Z unstable-options command line argument. When a feature is stabilized it becomes available on the stable release channel, and does not need to be explicitly enabled. At that point the feature is considered "ungated". Feature gates allow developers to test experimental features while they are under development, before they are available in the stable language.
While SipHash demonstrates competitive performance in many cases, one case where it is notably slower than other hashing algorithms is with short keys, such as integers. This is why Rust programmers often observe slow performance with HashMap. The FNV hasher is frequently recommended for these cases, but be aware that it does not have the same collision-resistance properties as SipHash.
Not generally, no. Tail-call optimization may be done in limited circumstances, but is not guaranteed. As the feature has always been desired, Rust has a keyword (become) reserved, though it is not clear yet whether it is technically possible, nor whether it will be implemented. There was a proposed extension that would allow tail-call elimination in certain contexts, but it is currently postponed.
Not in the typical sense used by languages such as Java, but parts of the Rust standard library can be considered a "runtime", providing a heap, backtraces, unwinding, and stack guards. There is a small amount of initialization code that runs before the user's main function. The Rust standard library additionally links to the C standard library, which does similar runtime initialization. Rust code can be compiled without the standard library, in which case the runtime is roughly equivalent to C's.
Use of curly braces to denote blocks is a common design choice in a variety of programming languages, and Rust's consistency is useful for people already familiar with the style.
Curly braces also allow for more flexible syntax for the programmer and a simpler parser in the compiler.
Whereas C requires mandatory parentheses for if-statement conditionals but leaves brackets optional, Rust makes the opposite choice for its if-expressions. This keeps the conditional clearly separate from the body and avoids the hazard of optional brackets, which can lead to easy-to-miss errors during refactoring, like Apple's goto fail bug.
Rust's overall design preference is for limiting the size of the language while enabling powerful libraries. While Rust does provide initialization syntax for arrays and string literals, these are the only collection types built into the language. Other library-defined types, including the ubiquitous Vec collection type, use macros for initialization like the vec! macro.
This design choice of using Rust's macro facilities to initialize collections will likely be extended generically to other collections in the future, enabling simple initialization of not only HashMap and Vec, but also other collection types such as BTreeMap. In the meantime, if you want a more convenient syntax for initializing collections, you can create your own macro to provide it.
Rust is a very expression-oriented language, and "implicit returns" are part of that design. Constructs like ifs, matches, and normal blocks are all expressions in Rust. For example, the following code checks if an i64 is odd, returning the result by simply yielding it as a value:
fnis_odd(x:i64) -> bool{if x % 2 != 0{true}else{false}}
Although it can be simplified even further like so:
fnis_odd(x:i64) -> bool{
x % 2 != 0}
In each example, the last line of the function is the return value of that function. It is important to note that if a function ends in a semicolon, its return type will be (), indicating no returned value. Implicit returns must omit the semicolon to work.
Explicit returns are only used if an implicit return is impossible because you are returning before the end of the function's body. While each of the above functions could have been written with a return keyword and semicolon, doing so would be unnecessarily verbose, and inconsistent with the conventions of Rust code.
Mandatory declaration signatures help enforce interface stability at both the module and crate level.
Signatures improve code comprehension for the programmer, eliminating the need for an IDE running an inference algorithm across an entire crate to be able to guess at a function's argument types; it's always explicit and nearby.
Mechanically, it simplifies the inference algorithm, as inference only requires looking at one function at a time.
First, if every possibility is covered by the match, adding variants to the enum in the future will cause a compilation failure, rather than an error at runtime. This type of compiler assistance makes fearless refactoring possible in Rust.
Second, exhaustive checking makes the semantics of the default case explicit: in general, the only safe way to have a non-exhaustive match would be to panic the thread if nothing is matched. Early versions of Rust did not require match cases to be exhaustive and it was found to be a great source of bugs.
It is easy to ignore all unspecified cases by using the _ wildcard:
The choice of which to use is dependent on the purpose of the program.
If you are interested in the greatest degree of precision with your floating point numbers, then prefer f64. If you are more interested in keeping the size of the value small or being maximally efficient, and are not concerned about the associated inaccuracy of having fewer bits per value, then f32 is better. Operations on f32 are usually faster, even on 64-bit hardware. As a common example, graphics programming typically uses f32 because it requires high performance, and 32-bit floats are sufficient for representing pixels on the screen.
If in doubt, choose f64 for the greater precision.
Floats can be compared with the ==, !=, <, <=, >, and >= operators, and with the partial_cmp() function. == and != are part of the PartialEq trait, while <, <=, >, >=, and partial_cmp() are part of the PartialOrd trait.
Floats cannot be compared with the cmp() function, which is part of the Ord trait, as there is no total ordering for floats. Furthermore, there is no total equality relation for floats, and so they also do not implement the Eq trait.
There is no total ordering or equality on floats because the floating-point value NaN is not less than, greater than, or equal to any other floating-point value or itself.
Because floats do not implement Eq or Ord, they may not be used in types whose trait bounds require those traits, such as BTreeMap or HashMap. This is important because these types assume their keys provide a total ordering or total equality relation, and will malfunction otherwise.
There is a crate that wraps f32 and f64 to provide Ord and Eq implementations, which may be useful in certain cases.
There are two ways: the as keyword, which does simple casting for primitive types, and the Into and From traits, which are implemented for a number of type conversions (and which you can implement for your own types). The Into and From traits are only implemented in cases where conversions are lossless, so for example, f64::from(0f32) will compile while f32::from(0f64) will not. On the other hand, as will convert between any two primitive types, truncating values as necessary.
Preincrement and postincrement (and the decrement equivalents), while convenient, are also fairly complex. They require knowledge of evaluation order, and often lead to subtle bugs and undefined behavior in C and C++. x = x + 1 or x += 1 is only slightly longer, but unambiguous.
Usually, you can pass a reference to a String or Vec<T> wherever a slice is expected. Using Deref coercions, Strings and Vecs will automatically coerce to their respective slices when passed by reference with & or & mut.
Methods implemented on &str and &[T] can be accessed directly on String and Vec<T>. For example, some_string.char_at(0) will work even though char_at is a method on &str and some_string is a String.
In some cases, such as generic code, it's necessary to convert manually. Manual conversions can be achieved using the slicing operator, like so: &my_vec[..].
The to_string() method converts from a &str into a String, and Strings are automatically converted into &str when you borrow a reference to them. Both are demonstrated in the following example:
fnmain(){let s = "Jane Doe".to_string();say_hello(&s);}fnsay_hello(name:&str){println!("Hello {}!", name);}
String is an owned buffer of UTF-8 bytes allocated on the heap. Mutable Strings can be modified, growing their capacity as needed. &str is a fixed-capacity "view" into a String allocated elsewhere, commonly on the heap, in the case of slices dereferenced from Strings, or in static memory, in the case of string literals.
&str is a primitive type implemented by the Rust language, while String is implemented in the standard library.
并不能这样做。At least not without a firm understanding of what you mean by "character", and preprocessing the string to find the index of the desired character.
Rust strings are UTF-8 encoded. A single visual character in UTF-8 is not necessarily a single byte as it would be in an ASCII-encoded string. Each byte is called a "code unit" (in UTF-16, code units are 2 bytes; in UTF-32 they are 4 bytes). "Code points" are composed of one or more code units, and combine in "grapheme clusters" which most closely approximate characters.
Thus, even though you may index on bytes in a UTF-8 string, you can't access the ith code point or grapheme cluster in constant time. However, if you know at which byte that desired code point or grapheme cluster begins, then you can access it in constant time. Functions including str::find() and regex matches return byte indices, facilitating this sort of access.
The str type is UTF-8 because we observe more text in the wild in this encoding – particularly in network transmissions, which are endian-agnostic – and we think it's best that the default treatment of I/O not involve having to recode codepoints in each direction.
This does mean that locating a particular Unicode codepoint inside a string is an O(n) operation, although if the starting byte index is already known then they can be accessed in O(1) as expected. On the one hand, this is clearly undesirable; on the other hand, this problem is full of trade-offs and we'd like to point out a few important qualifications:
Scanning a str for ASCII-range codepoints can still be done safely byte-at-a-time. If you use .as_bytes(), pulling out a u8 costs only O(1) and produces a value that can be cast and compared to an ASCII-range char. So if you're (say) line-breaking on '\n', byte-based treatment still works. UTF-8 was well-designed this way.
Most "character oriented" operations on text only work under very restricted language assumptions such as "ASCII-range codepoints only". Outside ASCII-range, you tend to have to use a complex (non-constant-time) algorithm for determining linguistic-unit (glyph, word, paragraph) boundaries anyway. We recommend using an "honest" linguistically-aware, Unicode-approved algorithm.
The char type is UTF-32. If you are sure you need to do a codepoint-at-a-time algorithm, it's trivial to write a type wstr = [char], and unpack a str into it in a single pass, then work with the wstr. In other words: the fact that the language is not "decoding to UTF32 by default" shouldn't stop you from decoding (or re-encoding any other way) if you need to work with that encoding.
For a more in-depth explanation of why UTF-8 is usually preferable over UTF-16 or UTF-32, read the UTF-8 Everywhere manifesto.
Rust has four pairs of string types, each serving a distinct purpose. In each pair, there is an "owned" string type, and a "slice" string type. The organization looks like this:
If the function needs an owned string, but wants to accept any type of string, use an Into<String> bound.
If the function needs a string slice, but wants to accept any type of string, use an AsRef<str> bound.
If the function does not care about the string type, and wants to handle the two possibilities uniformly, use Cow<str> as the input type.
使用 Into<String>
在此例中,the function will accept both owned strings and string slices, either doing nothing or converting the input into an owned string within the function body. Note that the conversion needs to be done explicitly, and will not happen otherwise.
fnaccepts_both<S:Into<String>>(s:S){let s = s.into();// This will convert s into a `String`.// ... the rest of the function}
使用 AsRef<str>
在此例中,the function will accept both owned strings and string slices, either doing nothing or converting the input into a string slice. This can be done automatically by taking the input by reference, like so:
fnaccepts_both<S:AsRef<str>>(s:&S){// ... the body of the function}
If your reason for implementing these data structures is to use them for other programs, there's no need, as efficient implementations of these data structures are provided by the standard library.
If, however, your reason is simply to learn, then you will likely need to dip into unsafe code. While these data structures can be implemented entirely in safe Rust, the performance is likely to be worse than it would be with the use of unsafe code. The simple reason for this is that data structures like vectors and linked lists rely on pointer and memory operations that are disallowed in safe Rust.
例如,a doubly-linked list requires that there be two mutable references to each node, but this violates Rust's mutable reference aliasing rules. You can solve this using Weak<T>, but the performance will be poorer than you likely want. With unsafe code you can bypass the mutable reference aliasing rule restriction, but must manually verify that your code introduces no memory safety violations.
The easiest way is by using the collection's IntoIterator implementation. Here is an example for &Vec:
let v = vec![1,2,3,4,5];for item in&v {print!("{} ", item);}println!("\nLength: {}", v.len());
Rust for 循环调用 into_iter() (defined on the IntoIterator trait) for whatever they're iterating over. Anything implementing the IntoIterator trait may be looped over with a for loop. IntoIterator is implemented for &Vec and &mut Vec, causing the iterator from into_iter() to borrow the contents of the collection, rather than moving/consuming them. The same is true for other standard collections as well.
You don't necessarily have to. If you're declaring an array directly, the size is inferred based on the number of elements. But if you're declaring a function that takes a fixed-size array, the compiler has to know how big that array will be.
One thing to note is that currently Rust doesn't offer generics over arrays of different size. If you'd like to accept a contiguous container of a variable number of values, use a Vec or slice (depending on whether you need ownership).
use std::cell::Cell;#[derive(Debug)]structUnmovable<'a>{x:u32,y:Cell<Option<&'a u32>>,}fnmain(){let test = Unmovable{x:42,y:Cell::new(None)};
test.y.set(Some(&test.x));println!("{:?}", test);}
These are different terms for the same thing. In all cases, it means the value has been moved to another owner, and moved out of the possession of the original owner, who can no longer use it. If a type implements the Copy trait, the original owner's value won't be invalidated, and can still be used.
This error means that the value you're trying to use has been moved to a new owner. The first thing to check is whether the move in question was necessary: if it moved into a function, it may be possible to rewrite the function to use a reference, rather than moving. Otherwise if the type being moved implements Clone, then calling clone() on it before moving will move a copy of it, leaving the original still available for further use. Note though that cloning a value should typically be the last resort since cloning can be expensive, causing further allocations.
If the moved value is of your own custom type, consider implementing Copy (for implicit copying, rather than moving) or Clone (explicit copying). Copy is most commonly implemented with #[derive(Copy, Clone)] (Copy requires Clone), and Clone with #[derive(Clone)].
If none of these are possible, you may want to modify the function that acquired ownership to return ownership of the value when the function exits.
First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:
one or more references (&T) to a resource.
exactly one mutable reference (&mut T)
While the rules themselves are simple, following them consistently is not, particularly for those unaccustomed to reasoning about lifetimes and ownership.
The first step in understanding the borrow checker is reading the errors it produces. A lot of work has been put into making sure the borrow checker provides quality assistance in resolving the issues it identifies. When you encounter a borrow checker problem, the first step is to slowly and carefully read the error reported, and to only approach the code after you understand the error being described.
The second step is to become familiar with the ownership and mutability-related container types provided by the Rust standard library, including Cell, RefCell, and Cow. These are useful and necessary tools for expressing certain ownership and mutability situations, and have been written to be of minimal performance cost.
The single most important part of understanding the borrow checker is practice. Rust's strong static analyses guarantees are strict and quite different from what many programmers have worked with before. It will take some time to become completely comfortable with everything.
If you find yourself struggling with the borrow checker, or running out of patience, always feel free to reach out to the Rust community for help.
This is covered in the official documentation for Rc, Rust's non-atomically reference-counted pointer type. In short, Rc and its thread-safe cousin Arc are useful to express shared ownership, and have the system automatically deallocate the associated memory when no one has access to it.
To return a closure from a function, it must be a "move closure", meaning that the closure is declared with the move keyword. As explained in the Rust book, this gives the closure its own copy of the captured variables, independent of its parent stack frame. Otherwise, returning a closure would be unsafe, as it would allow access to variables that are no longer valid; put another way: it would allow reading potentially invalid memory. The closure must also be wrapped in a Box, so that it is allocated on the heap. Read more about this in the book.
A deref coercion is a handy coercion that automatically converts references to pointers (e.g., &Rc<T> or &Box<T>) into references to their contents (e.g., &T). Deref coercions exist to make using Rust more ergonomic, and are implemented via the Deref trait.
A Deref implementation indicates that the implementing type may be converted into a target by a call to the deref method, which takes an immutable reference to the calling type and returns a reference (of the same lifetime) to the target type. The * prefix operator is shorthand for the deref method.
They're called"coercions" because of the following rule, quoted here from the Rust book:
If you have a type U, and it implements Deref<Target=T>, values of &U will automatically coerce to a &T.
For example, if you have a &Rc<String>, it will coerce via this rule into a &String, which then coerces to a &str in the same way. So if a function takes a &str parameter, you can pass in a &Rc<String> directly, with all coercions handled automatically via the Deref trait.
Lifetimes are Rust's answer to the question of memory safety. They allow Rust to ensure memory safety without the performance costs of garbage collection. They are based on a variety of academic work, which can be found in the Rust book.
The 'a syntax comes from the ML family of programming languages, where 'a is used to indicate a generic type parameter. For Rust, the syntax had to be something that was unambiguous, noticeable, and fit nicely in a type declaration right alongside traits and references. Alternative syntaxes have been discussed, but no alternative syntax has been demonstrated to be clearly better.
You need to ensure that the borrowed item will outlive the function. This can be done by binding the output lifetime to some input lifetime like so:
typePool = TypedArena<Thing>;// (the lifetime below is only written explicitly for// expository purposes; it can be omitted via the// elision rules described in a later FAQ entry)fncreate_borrowed<'a>(pool:&'a Pool,x:i32,y:i32) -> &'a Thing{
pool.alloc(Thing{x: x,y: y })}
An alternative is to eliminate the references entirely by returning an owning type like String:
fnhappy_birthday(name:&str,age:i64) -> String{format!("Hello {}! You're {} years old!", name, age)}
In fact, all reference types have a lifetime, but most of the time you do not have to write it explicitly. The rules are as follows:
Within a function body, you never have to write a lifetime explicitly; the correct value should always be inferred.
Within a function signature (for example, in the types of its arguments, or its return type), you may have to write a lifetime explicitly. Lifetimes there use a simple defaulting scheme called "lifetime elision", which consists of the following three rules:
Each elided lifetime in a function’s arguments becomes a distinct lifetime parameter.
If there is exactly one input lifetime, elided or not, that lifetime is assigned to all elided lifetimes in the return values of that function.
If there are multiple input lifetimes, but one of them is &self or &mut self, the lifetime of self is assigned to all elided output lifetimes.
Finally, in a struct or enum definition, all lifetimes must be explicitly declared.
If these rules result in compilation errors, the Rust compiler will provide an error message indicating the error caused, and suggesting a potential solution based on which step of the inference process caused the error.
The only way to construct a value of type &Foo or &mut Foo is to specify an existing value of type Foo that the reference points to. The reference "borrows" the original value for a given region of code (the lifetime of the reference), and the value being borrowed from cannot be moved or destroyed for the duration of the borrow.
You can do that with the Option type, which can either be Some(T) or None. Some(T) indicates that a value of type T is contained within, while None indicates the absence of a value.
Monomorphisation specializes each use of a generic function (or structure) with specific instance, based on the parameter types of calls to that function (or uses of the structure).
During monomorphisation a new copy of the generic function is translated for each unique set of types the function is instantiated with. This is the same strategy used by C++. It results in fast code that is specialized for every call-site and statically dispatched, with the tradeoff that functions instantiated with many different types can cause "code bloat", where multiple function instances result in larger binaries than would be created with other translation strategies.
Functions that accept trait objects instead of type parameters do not undergo monomorphisation. Instead, methods on the trait objects are dispatched dynamically at runtime.
Functions and closures are operationally equivalent, but have different runtime representations due to their differing implementations.
Functions are a built-in primitive of the language, while closures are essentially syntactic sugar for one of three traits: Fn, FnMut, and FnOnce. When you make a closure, the Rust compiler automatically creates a struct implementing the appropriate trait of those three and containing the captured environment variables as members, and makes it so the struct can be called as a function. Bare functions can not capture an environment.
The big difference between these traits is how they take the self parameter. Fn takes &self, FnMut takes &mut self, and FnOnce takes self.
Even if a closure does not capture any environment variables, it is represented at runtime as two pointers, the same as any other closure.
Higher-kinded types are types with unfilled parameters. Type constructors, like Vec, Result, and HashMap are all examples of higher-kinded types: each requires some additional type parameters in order to actually denote a specific type, like Vec<u32>. Support for higher-kinded types means these "incomplete" types may be used anywhere "complete" types can be used, including as generics for functions.
Any complete type, like i32, bool, or char is of kind * (this notation comes from the field of type theory). A type with one parameter, like Vec<T> is of kind * -> *, meaning that Vec<T> takes in a complete type like i32 and returns a complete type Vec<i32>. A type with three parameters, like HashMap<K, V, S> is of kind * -> * -> * -> *, and takes in three complete types (like i32, String, and RandomState) to produce a new complete type HashMap<i32, String, RandomState>.
In addition to these examples, type constructors can take lifetime arguments, which we'll denote as Lt. For example, slice::Iter has kind Lt -> * -> *, because it must be instantiated like Iter<'a, u32>.
The lack of support for higher-kinded types makes it difficult to write certain kinds of generic code. It's particularly problematic for abstracting over concepts like iterators, since iterators are often parameterized over a lifetime at least. That in turn has prevented the creation of traits abstracting over Rust's collections.
Another common example is concepts like functors or monads, both of which are type constructors, rather than single types.
Rust doesn't currently have support for higher-kinded types because it hasn't been a priority compared to other improvements we want to make. Since the design is a major, cross-cutting change, we also want to approach it carefully. But there's no inherent reason for the current lack of support.
These are called associated types, and they allow for the expression of trait bounds that can't be expressed with a where clause. For example, a generic bound X: Bar<T=Foo> means "X must implement the trait Bar, and in that implementation of Bar, X must choose Foo for Bar's associated type, T." Examples of where such a constraint cannot be expressed via a where clause include trait objects like Box<Bar<T=Foo>>.
Associated types exist because generics often involve families of types, where one type determines all of the others in a family. For example, a trait for graphs might have as its Self type the graph itself, and have associated types for nodes and for edges. Each graph type uniquely determines the associated types. Using associated types makes it much more concise to work with these families of types, and also provides better type inference in many cases.
There are some types in Rust whose values are only partially ordered, or have only partial equality. Partial ordering means that there may be values of the given type that are neither less than nor greater than each other. Partial equality means that there may be values of the given type that are not equal to themselves.
Floating point types (f32 and f64) are good examples of each. Any floating point type may have the value NaN (meaning "not a number"). NaN is not equal to itself (NaN == NaN is false), and not less than or greater than any other floating point value. As such, both f32 and f64 implement PartialOrd and PartialEq but not Ord and not Eq.
As explained in the earlier question on floats, these distinctions are important because some collections rely on total orderings/equality in order to give correct results.
The File type implements the Read trait, which has a variety of functions for reading and writing data, including read(), read_to_end(), bytes(), chars(), and take(). Each of these functions reads a certain amount of input from a given file. read() reads as much input as the underlying system will provide in a single call. read_to_end() reads the entire buffer into a vector, allocating as much space as is needed. bytes() and chars() allow you to iterate over the bytes and characters of the file, respectively. Finally, take() allows you to read up to an arbitrary number of bytes from the file. Collectively, these should allow you to efficiently read in any data you need.
For buffered reads, use the BufReader struct, which helps to reduce the number of system calls when reading.
Exceptions complicate understanding of control-flow, they express validity/invalidity outside of the type system, and they interoperate poorly with multithreaded code (a major focus of Rust).
Rust prefers a type-based approach to error handling, which is covered at length in the book. This fits more nicely with Rust's control flow, concurrency, and everything else.
unwrap() is a function that extracts the value inside an Option or Result and panics if no value is present.
unwrap() shouldn't be your default way to handle errors you expect to arise, such as incorrect user input. In production code, it should be treated like an assertion that the value is non-empty, which will crash the program if violated.
It's also useful for quick prototypes where you don't want to handle an error yet, or blog posts where error handling would distract from the main point.
It's probably an issue with the function's return type. The try! macro either extracts the value from a Result, or returns early with the error Result is carrying. This means that try only works for functions that return Result themselves, where the Err-constructed type implements From::from(err). In particular, this means that the try! macro cannot work inside the main function.
If you're looking for a way to avoid handling Results in other people's code, there's always unwrap(), but it's probably not what you want. Result is an indicator that some computation may or may not complete successfully. Requiring you to handle these failures explicitly is one of the ways that Rust encourages robustness. Rust provides tools like the try! macro to make handling failures ergonomic.
If you really don't want to handle an error, use unwrap(), but know that doing so means that the code panics on failure, which usually results in a shutting down the process.
Mutation is safe if it's synchronized. Mutating a static Mutex (lazily initialized via the lazy-static crate) does not require an unsafe block, nor does mutating a static AtomicUsize (which can be initialized without lazy_static).
Not currently. Rust macros are "hygienic macros", which intentionally avoid capturing or creating identifiers that may cause unexpected collisions with other identifiers. Their capabilities are significantly different than the style of macros commonly associated with the C preprocessor. Macro invocations can only appear in places where they are explicitly supported: items, method declarations, statements, expressions, and patterns. Here, "method declarations" means a blank space where a method can be put. They can't be used to complete a partial method declaration. By the same logic, they can't be used to complete a partial variable declaration.
Rust programs can be debugged using gdb or lldb, the same as C and C++. In fact, every Rust installation comes with one or both of rust-gdb and rust-lldb (depending on platform support). These are wrappers over gdb and lldb with Rust pretty-printing enabled.
This error is usually caused by unwrap()ing a None or Err in client code. Enabling backtraces by setting the environment variable RUST_BACKTRACE=1 helps with getting more information. Compiling in debug mode (the default for cargo build) is also helpful. Using a debugger like the provided rust-gdb or rust-lldb is also helpful.
默认不会。在一般情况下,enum 和 struct 布局是未定义的。这允许编译器进行潜在优化,例如重新使用 padding for the discriminant, compacting variants of nested enums, reordering fields to remove padding, etc. enums which carry no data ("C-like") are eligible to have a defined representation. Such enums are easily distinguished in that they are simply a list of names that carry no data:
enumCLike{A,B = 32,C = 34,D}
#[repr(C)] 属性可以应用到诸如 enums 以提供等同 C 语言中的表示。This allows using Rust enums in FFI code where C enums are also used, for most use cases. The attribute can also be applied to structs to get the same layout as a C struct would.
There are a number of possible answers, but a common mistake is not realizing that use declarations are relative to the crate root. Try rewriting your declarations to use the paths they would use if defined in the root file of your project and see if that fixes the problem.
There are also self and super, which disambiguate use paths as being relative to the current module or parent module, respectively.
For complete information on useing libraries, read the Rust book's chapter "Crates and Modules".
For methods defined on a trait, you have to explicitly import the trait declaration. This means it's not enough to import a module where a struct implements the trait, you must also import the trait itself.
It probably could, but you also don't want it to. While in many cases it is likely that the compiler could determine the correct module to import by simply looking for where a given identifier is defined, this may not be the case in general. Any decision rule in rustc for choosing between competing options would likely cause surprise and confusion in some cases, and Rust prefers to be explicit about where names are coming from.
For example, the compiler could say that in the case of competing identifier definitions the definition from the earliest imported module is chosen. So if both module foo and module bar define the identifier baz, but foo is the first registered module, the compiler would insert use foo::baz;.
mod foo;mod bar;// use foo::baz // to be inserted by the compiler.fnmain(){baz();}
If you know this is going to happen, perhaps it saves a small number of keystrokes, but it also greatly increases the possibility for surprising error messages when you actually meant for baz() to be bar::baz(), and it decreases the readability of the code by making the meaning of a function call dependent on module declaration. These are not tradeoffs we are willing to make.
In the first month with crates.io, a number of people have asked us about the possibility of introducing namespaced packages.
While namespaced packages allow multiple authors to use a single, generic name, they add complexity to how packages are referenced in Rust code and in human communication about packages. At first glance, they allow multiple authors to claim names like http, but that simply means that people will need to refer to those packages as wycats' http or reem's http, offering little benefit over package names like wycats-http or reem-http.
When we looked at package ecosystems without namespacing, we found that people tended to go with more creative names (like nokogiri instead of "tenderlove's libxml2"). These creative names tend to be short and memorable, in part because of the lack of any hierarchy. They make it easier to communicate concisely and unambiguously about packages. They create exciting brands. And we've seen the success of several 10,000+ package ecosystems like NPM and RubyGems whose communities are prospering within a single namespace.
In short, we don't think the Cargo ecosystem would be better off if Piston chose a name like bvssvni/game-engine (allowing other users to choose wycats/game-engine) instead of simply piston.
Because namespaces are strictly more complicated in a number of ways, and because they can be added compatibly in the future should they become necessary, we're going to stick with a single shared namespace.
That depends. There are ways of translating object-oriented concepts like multiple inheritance to Rust, but as Rust is not object-oriented the result of the translation may look substantially different from its appearance in an OO language.
The easiest way is to use the Option type in whatever function you're using to construct instances of the struct (usually new()). Another way is to use the builder pattern, where only certain functions instantiating member variables must be called before the construction of the built type.
Rust 中可以用 const 声明在编译时计算的全局常量,而 static 可用于可变的全局变量。请注意,修改一个 static mut 变量需要使用 unsafe,因为它允许数据竞争(races), one of the things guaranteed not to happen in safe Rust. const 与 static 值的一项重要区别是,你可以引用 static 值,但不能引用 const 值,因为它没有一个特定的内存位置。有关 const 与 static 的更多信息,请阅读 Rust 之书。
To define procedural constants that can't be defined via these mechanisms, use the lazy-static crate, which emulates compile-time evaluation by automatically evaluating the constant at first use.
No. Globals cannot have a non-constant-expression constructor and cannot have a destructor at all. Static constructors are undesirable because portably ensuring a static initialization order is difficult. Life before main is often considered a misfeature, so Rust does not allow it.
See the C++ FQA about the "static initialization order fiasco", and Eric Lippert's blog for the challenges in C#, which also has this feature.
There are several factors that contribute to Rust programs having, by default, larger binary sizes than functionally-equivalent C programs. In general, Rust's preference is to optimize for the performance of real-world programs, not the size of small programs.
Monomorphization
Rust monomorphizes generics, meaning that a new version of a generic function or type is generated for each concrete type it's used with in the program. This is similar to how templates work in C++. For example, in the following program:
fnfoo<T>(t:T){// ... do something}fnmain(){foo(10);// i32foo("hello");// &str}
Two distinct versions of foo will be in the final binary, one specialized to an i32 input, one specialized to a &str input. This enables efficient static dispatch of the generic function, but at the cost of a larger binary.
调试符号
Rust programs compile with some debug symbols retained, even when compiling in release mode. These are used for providing backtraces on panics, and can be removed with strip, or another debug symbol removal tool. It is also useful to note that compiling in release mode with Cargo is equivalent to setting optimization level 3 with rustc. An alternative optimization level (called s or z) has recently landed and tells the compiler to optimize for size rather than performance.
Jemalloc
Rust uses jemalloc as the default allocator, which adds some size to compiled Rust binaries. Jemalloc is chosen because it is a consistent, quality allocator that has preferable performance characteristics compared to a number of common system-provided allocators. There is work being done to make it easier to use custom allocators, but that work is not yet finished.
链接时优化
Rust does not do link-time optimization by default, but can be instructed to do so. This increases the amount of optimization that the Rust compiler can potentially do, and can have a small effect on binary size. This effect is likely larger in combination with the previously mentioned size optimizing mode.
标准库
The Rust standard library includes libbacktrace and libunwind, which may be undesirable in some programs. Using #![no_std] can thus result in smaller binaries, but will also usually result in substantial changes to the sort of Rust code you're writing. Note that using Rust without the standard library is often functionally closer to the equivalent C code.
use std::io;fnmain(){println!("What's your name?");letmut input = String::new();
io::stdin().read_line(&mut input).unwrap();println!("Hello {}!", input);}
This program, when compiled and compared against the C program, will have a larger binary and use more memory. But this program is not exactly equivalent to the above C code. The equivalent Rust code would instead look something like this:
Which should indeed roughly match C in memory usage, at the expense of more programmer complexity, and a lack of static guarantees usually provided by Rust (avoided here with the use of unsafe).
Committing to an ABI is a big decision that can limit potentially advantageous language changes in the future. Given that Rust only hit 1.0 in May of 2015, it is still too early to make a commitment as big as a stable ABI. This does not mean that one won't happen in the future, though. (Though C++ has managed to go for many years without specifying a stable ABI.)
The extern keyword allows Rust to use specific ABI's, such as the well-defined C ABI, for interop with other languages.
可以。The Rust code has to be exposed via an extern declaration, which makes it C-ABI compatible. Such a function can be passed to C code as a function pointer or, if given the #[no_mangle] attribute to disable symbol mangling, can be called directly from C code.
Modern C++ includes many features that make writing safe and correct code less error-prone, but it's not perfect, and it's still easy to introduce unsafety. This is something the C++ core developers are working to overcome, but C++ is limited by a long history that predates a lot of the ideas they are now trying to implement.
Rust was designed from day one to be a safe systems programming language, which means it's not limited by historic design decisions that make getting safety right in C++ so complicated. In C++, safety is achieved by careful personal discipline, and is very easy to get wrong. In Rust, safety is the default. It gives you the ability to work in a team that includes people less perfect than you are, without having to spend your time double-checking their code for safety bugs.
The underlying concepts are similar, but the two systems work very differently in practice. In both systems, "moving" a value is a way to transfer ownership of its underlying resources. For example, moving a string would transfer the string's buffer rather than copying it.
In Rust, ownership transfer is the default behavior. For example, if I write a function that takes a String as argument, this function will take ownership of the String value supplied by its caller:
fnprocess(s:String){}fncaller(){let s = String::from("Hello, world!");process(s);// Transfers ownership of `s` to `process`process(s);// Error! ownership already transferred.}
As you can see in the snippet above, in the function caller, the first call to process transfers ownership of the variable s. The compiler tracks ownership, so the second call to process results in an error, because it is illegal to give away ownership of the same value twice. Rust will also prevent you from moving a value if there is an outstanding reference into that value.
C++ takes a different approach. In C++, the default is to copy a value (to invoke the copy constructor, more specifically). However, callees can declare their arguments using an "rvalue reference", like string&&, to indicate that they will take ownership of some of the resources owned by that argument (in this case, the string's internal buffer). The caller then must either pass a temporary expression or make an explicit move using std::move. The rough equivalent to the function process above, then, would be:
C++ 编译器 are not obligated to track moves. For example, the code above compiles without a warning or error, at least using the default settings on clang. Moreover, in C++ ownership of the string s itself (if not its internal buffer) remains with caller, and so the destructor for s will run when caller returns, even though it has been moved (in Rust, in contrast, moved values are dropped only by their new owners).
没有。Functions serve the same purpose as constructors without adding language complexity. The usual name for the constructor-equivalent function in Rust is new(), although this is just a convention rather than a language rule. The new() function in fact is just like any other function. An example of it looks like so:
不太完全。Types which implement Copy will do a standard C-like "shallow copy" with no extra work (similar to "plain old data" in C++). It is impossible to implement Copy types that require custom copy behavior. Instead, in Rust "copy constructors" are created by implementing the Clone trait, and explicitly calling the clone method. Making user-defined copy operators explicit surfaces the underlying complexity, making it easier for the developer to identify potentially expensive operations.
没有。Values of all types are moved via memcpy. This makes writing generic unsafe code much simpler since assignment, passing and returning are known to never have a side effect like unwinding.
Rust 与 Go 有着完全不同的设计目标。下列差异不是全部的差异(它们太多而无法列出),而是较重要的一些:
Rust is lower level than Go. For example, Rust does not require a garbage collector, whereas Go does. In general, Rust affords a level of control that is comparable to C or C++.
Rust's focus is on ensuring safety and efficiency while also providing high-level affordances, while Go's is on being a small, simple language which compiles quickly and can work nicely with a variety of tools.
Rust has strong support for generics, which Go does not.
Rust has strong influences from the world of functional programming, including a type system which draws from Haskell's typeclasses. Go has a simpler type system, using interfaces for basic generic programming.
Rust traits are similar to Haskell typeclasses, but are currently not as powerful, as Rust cannot express higher-kinded types. Rust's associated types are equivalent to Haskell type families.
Some specific difference between Haskell typeclasses and Rust traits include:
Rust traits have an implicit first parameter called Self. trait Bar in Rust corresponds to class Bar self in Haskell, and trait Bar<Foo> in Rust corresponds to class Bar foo self in Haskell.
"Supertraits" or "superclass constraints" in Rust are written trait Sub: Super, compared to class Super self => Sub self in Haskell.
Rust forbids orphan instances, resulting in different coherence rules in Rust compared to Haskell.
Rust's impl resolution considers the relevant where clauses and trait bounds when deciding whether two impls overlap, or choosing between potential impls. Haskell only considers the constraints in the instance declaration, disregarding any constraints provided elsewhere.
A subset of Rust's traits (the "object safe" ones) can be used for dynamic dispatch via trait objects. The same feature is available in Haskell via GHC's ExistentialQuantification.
When you use cargo doc to generate documentation for your own project, it also generates docs for the active dependency versions. These are put into the target/doc directory of your project. Use cargo doc --open to open the docs after building them, or just open up target/doc/index.html yourself.
https://github.com/jethrogb/rust-www/blob/master/zh-CN/faq.md
常被问到的问题
此页面回答了有关 Rust 编程语言的常见问题。它不是这个语言的完整指南,也不是教学该语言的工具。这是 Rust 社区常遇到的人们重复提问的回答,及澄清了 Rust 某些设计决策幕后的理由。
如果你觉得有一些常见或重要的问题没有在此列出,欢迎帮我们解决。
目录
Rust 项目
此项目的目标是什么?
设计和实现一个安全、并发、实用的系统语言。
Rust 存在的原因是,其他语言在这个抽象层次和效率上不能令人满意。尤其是:
Rust 作为一种备选方案,可以提供高效的代码和舒适的抽象级别,同时改进上述四项要点。
这个项目由 Mozilla 控制?
不是。Rust 始于 2006 年 Graydon Hoare 的兼职项目,并持续了三年。Mozilla 在语言成熟到足以运行基本测试并展示其核心概念时于 2009 年开始参与。虽然它由 Mozilla 资助,但 Rust 是由来自世界各地的多样化的发烧友开发的。Rust 团队 由 Mozilla 和非 Mozilla 成员组成,
rustc
(Rust 的编译器)至今也有超过一千名独立贡献者。就项目治理而言, Rust 由一个从全局角度出发,为项目设定愿景和优先事项的核心团队管理。 我们还有小组指导和促进特定兴趣领域的发展,包括核心语言、编译器、Rust 库、Rust 工具和官方 Rust 社区的管理。这些领域中的设计都先进地经过一个RFC 流程。对于不需要 RFC 的更改,决定是通过
rustc
代码库上的拉取请求决定。哪些不是 Rust 的目标?
Mozilla 的哪些项目使用 Rust?
主要项目是 Servo,它是 Mozilla 正在雕琢的一个实验性的浏览器引擎。Mozilla 还在努力整合 Rust 组件 到 Firefox。
有哪些大型的 Rust 项目?
目前两个最大的 Rust 开源项目是 Servo 和 Rust 编译器 本身。
还有谁在使用 Rust?
越来越多的组织在使用 Rust!
怎样轻松地尝试 Rust?
playpen 是尝试 Rust 的最简单方法,它是一个可编写和运行 Rust 代码的在线应用。如果您想在您的系统上尝试 Rust,安装并尝试猜谜游戏教程。
如何就 Rust 问题取得帮助?
那有很多种方式。您可以尝试:
为什么 Rust 随时间推移在大幅变化?
Rust 起步的目标是创建一个安全且合用的系统编程语言。为了追求这个目标,它探索了很多想法,其中一些(生命周期、traits)被保留,而其他则被丢弃(类型体系系统、绿色线程)。 Also, in the run up to 1.0 a lot of the standard library was rewritten as early designs were updated to best use Rust's features and provide quality, consistent cross-platform APIs. 现在的 Rust 已达到了 1.0,语言保证为 “稳定”(stable);虽然它可能继续演变,但对于目前的 Rust 来说,代码在未来的发行版本上能继续工作。
Rust 语言的版本控制是怎样的?
Rust 的语言版本遵循 SemVer,如果更改修复了编译器错误、补丁安全漏洞,或更改调度或类型推断需要附加注明,则稳定 API 的向后不兼容更改允许在次要版本中出现。次要版本更改的更详细指南可以在语言和标准库的已批准 RFC 中找到。
Rust 维护三个 “发行频道”:稳定版(stable)、测试版(beta)和每夜版(nightly)。稳定版和测试版每六周更新一次,而在那时的每夜版会变为新的测试版,测试版变为新的稳定版。标记为不稳定或者隐藏在特性门控后的语言和标准库特性只能在每夜版上使用,新特性定位为不稳定,一旦被核心团队和相关的子团队批准的话是 “无门控的”,这种方法允许实验性变更,并同时为稳定频道提供强有力的向后兼容保证。
就相关的其他详细信息,请阅读 Rust 博客"Stability as a Deliverable."
我可以在 Beta 或稳定频道上使用不稳定的功能吗?
并不能。Rust 努力保证测试版和每夜版的特性稳定性。当某些特性不稳定时,意味着我们还不能提供这种保证,不希望开发者依赖它。这给了我们在每夜版上适时尝试改变的机会,但是继续维护开发者寻求的稳定性。
测试和稳定版每六周更新一次是固定的,偶然有测试版的修正被及时接受,你不想使用每夜版而等待希望的某个特性的话,你可以通过检查
B-unstable
标记跟踪尚存的问题。什么是 “特性门控”(Feature Gates)?
“特性门控”(Feature Gates)是 Rust 用来稳定编译器、语言和标准库特性的机制。 A feature that is "gated" is accessible only on the nightly release channel, and then only when it has been explicitly enabled through
#[feature]
attributes or the-Z unstable-options
command line argument. When a feature is stabilized it becomes available on the stable release channel, and does not need to be explicitly enabled. At that point the feature is considered "ungated". Feature gates allow developers to test experimental features while they are under development, before they are available in the stable language.为什么采用 MIT/ASL2 双许可证?
Apache 许可证包含对专利侵权的重要保护,但它与 GPL 第二版不兼容。为避免 Rust 使用 GPL2 会遇到的问题,因而同时采用 MIT 许可证。
为什么是 BSD 风格的许可证,而不是 MPL 或三个许可证?
这一部分是由于原始开发者(Graydon)的偏好,另一部分原因在于,语言倾向于拥有更广泛的受众群体,以及将其更多样的嵌入和提供到终端,例如网页浏览器。。我们希望呼吁尽可能多地潜在贡献者。
性能
Rust 有多快?
飞快!Rust 在一些基准测试中(例如 Benchmarks Game、等等)已经有与惯用的 C 和 C++ 竞争的能力。
与 C++ 类似,Rust 采用零成本抽象作为其核心原则之一:Rust 的抽象都没有施加全局性能损失,也不会有任何运行时系统的开销。
鉴于 Rust 建立在 LLVM 上,以及力求从 LLVM 的角度来看 C 语言,任何 LLVM 的性能改进也都有助于 Rust。从长远来看,Rust 的类型系统中更丰富的信息也应该能够实现对于 C / C++ 代码来说困难或不可能的优化。
Rust 会垃圾收集吗?
不。Rust 的重要创新之一就是保证内存安全(无 segfaults),_无需_垃圾收集。
通过避免垃圾收集(GC),Rust 可以提供许多益处:可预测的资源清理,降低内存管理开销,基本上没有运行时系统。这些特征使 Rust 很容易嵌入到任意上下文,并使其更容易集成 Rust 代码到有 GC 的语言。
Rust 通过其所有权和借用(borrowing)系统避免了垃圾收集的需求,而同一系统也有助于解决许多其他问题,包括 一般的资源管理和并发。
如果单一的所有权不够,Rust 程序依赖标准的引用计数智能指针类型,
Rc
,及其线程安全版的Arc
,而不是 GC。不过,我们也在调查_可选_的垃圾收集作为一项未来扩展。 其目标旨在顺利与有垃圾收集的运行时整合, 例如由 Spidermonkey 和 V8 JavaScript 引擎所提供的那些。 最后,有人已调研了无编译器支持实现的 纯 Rust 垃圾收集器 。
为什么我的程序很慢?
如非要求,Rust 编译器不会优化编译,因为优化会编译速度,且在开发过程中通常是不合需要。
如果您使用
cargo
编译,请使用--release
标志。如果您直接使用rustc
,使用-O
标志。两者都将优化编译。Rust 编译似乎很慢。这是为什么?
代码转换和优化。Rust 提供了高级抽象,它可以编译成高效的机器代码,而这些翻译需要时间来运行,特别是优化。
但 Rust 的编译时间并不像看起来那么糟糕,并有理由相信会有所改善。当比较 C++ 与 Rust 的类似大小的项目时,整个项目的编译时间一般被认为是可比的。Rust 编译缓慢的一般认识很大程度上是由于 C++ 与 Rust 的_编译模型_的差异:C++ 的编译单元是文件,而 Rust 则是包装箱,它由很多文件组成。因此,在开发过程中,修改单个 C++ 文件导致的重新编译比 Rust 更少。正在进行的重大工作将重构编译器来引入增量编译,这将使 Rust 的编译时间变得比 C++ 的模型更有优势。
除了编译模型外,Rust 的语言设计和编译器实现的其他几个方面也影响了编译时性能。
首先,Rust 有中等复杂类型的系统,并且必须花费不可忽略的编译时间来强制在运行时使 Rust 安全的约束。
其次,Rust 编译器遭受着长期的技术债务,特别是生成质量差的 LLVM IR,而 LLVM 必须花时间 “修复”。这有希望在未来基于 MIR优化和转换传递来减轻 Rust 编译器在 LLVM 上的负担。
第三,Rust 使用的 LLVM 代码生成是一把双刃剑:虽然它能够使 Rust 具有世界一流的运行时性能,但 LLVM 是一个不重视编译时性能的大型框架,特别是在使用较差输入质量时。
最后,虽然 Rust 的单性泛型(ala C ++)的首选策略产生快速代码,但它需要比其他翻译策略产生更多的代码。Rust 程序员可以使用 trait 对象通过动态调度来抵消这个代码膨胀。
为什么 Rust 的
HashMap
很慢?默认情况下,Rust 的
HashMap
使用 SipHash 哈希算法,which is designed to prevent hash table collision attacks while providing reasonable performance on a variety of workloads.While SipHash demonstrates competitive performance in many cases, one case where it is notably slower than other hashing algorithms is with short keys, such as integers. This is why Rust programmers often observe slow performance with
HashMap
. The FNV hasher is frequently recommended for these cases, but be aware that it does not have the same collision-resistance properties as SipHash.为什么没有集成的基准测试基础设施?
有,但它只在夜间发行频道上可用。我们最终计划为集成的基准建立一个可插拔系统,但同时,目前的系统被认为是不稳定的。
Rust 是否做尾呼优化?
Not generally, no. Tail-call optimization may be done in limited circumstances, but is not guaranteed. As the feature has always been desired, Rust has a keyword (
become
) reserved, though it is not clear yet whether it is technically possible, nor whether it will be implemented. There was a proposed extension that would allow tail-call elimination in certain contexts, but it is currently postponed.Rust 有运行时吗?
Not in the typical sense used by languages such as Java, but parts of the Rust standard library can be considered a "runtime", providing a heap, backtraces, unwinding, and stack guards. There is a small amount of initialization code that runs before the user's
main
function. The Rust standard library additionally links to the C standard library, which does similar runtime initialization. Rust code can be compiled without the standard library, in which case the runtime is roughly equivalent to C's.语法
Why curly braces? Why can't Rust's syntax be like Haskell's or Python's?
Use of curly braces to denote blocks is a common design choice in a variety of programming languages, and Rust's consistency is useful for people already familiar with the style.
Curly braces also allow for more flexible syntax for the programmer and a simpler parser in the compiler.
I can leave out parentheses on
if
conditions, so why do I have to put brackets around single line blocks? Why is the C style not allowed?Whereas C requires mandatory parentheses for
if
-statement conditionals but leaves brackets optional, Rust makes the opposite choice for itsif
-expressions. This keeps the conditional clearly separate from the body and avoids the hazard of optional brackets, which can lead to easy-to-miss errors during refactoring, like Apple's goto fail bug.Why is there no literal syntax for dictionaries?
Rust's overall design preference is for limiting the size of the language while enabling powerful libraries. While Rust does provide initialization syntax for arrays and string literals, these are the only collection types built into the language. Other library-defined types, including the ubiquitous
Vec
collection type, use macros for initialization like thevec!
macro.This design choice of using Rust's macro facilities to initialize collections will likely be extended generically to other collections in the future, enabling simple initialization of not only
HashMap
andVec
, but also other collection types such asBTreeMap
. In the meantime, if you want a more convenient syntax for initializing collections, you can create your own macro to provide it.我什么时候应该使用隐式 return?
Rust is a very expression-oriented language, and "implicit returns" are part of that design. Constructs like
if
s,match
es, and normal blocks are all expressions in Rust. For example, the following code checks if ani64
is odd, returning the result by simply yielding it as a value:Although it can be simplified even further like so:
In each example, the last line of the function is the return value of that function. It is important to note that if a function ends in a semicolon, its return type will be
()
, indicating no returned value. Implicit returns must omit the semicolon to work.Explicit returns are only used if an implicit return is impossible because you are returning before the end of the function's body. While each of the above functions could have been written with a
return
keyword and semicolon, doing so would be unnecessarily verbose, and inconsistent with the conventions of Rust code.为什么不推断函数签名?
在 Rust 中,声明倾向于使用显式类型,而实际代码则推断其类型。这种设计有几个原因:
Why does
match
have to be exhaustive?To aid in refactoring and clarity.
First, if every possibility is covered by the
match
, adding variants to theenum
in the future will cause a compilation failure, rather than an error at runtime. This type of compiler assistance makes fearless refactoring possible in Rust.Second, exhaustive checking makes the semantics of the default case explicit: in general, the only safe way to have a non-exhaustive
match
would be to panic the thread if nothing is matched. Early versions of Rust did not requirematch
cases to be exhaustive and it was found to be a great source of bugs.It is easy to ignore all unspecified cases by using the
_
wildcard:数字
Which of
f32
andf64
should I prefer for floating-point math?The choice of which to use is dependent on the purpose of the program.
If you are interested in the greatest degree of precision with your floating point numbers, then prefer
f64
. If you are more interested in keeping the size of the value small or being maximally efficient, and are not concerned about the associated inaccuracy of having fewer bits per value, thenf32
is better. Operations onf32
are usually faster, even on 64-bit hardware. As a common example, graphics programming typically usesf32
because it requires high performance, and 32-bit floats are sufficient for representing pixels on the screen.If in doubt, choose
f64
for the greater precision.Why can't I compare floats or use them as
HashMap
orBTreeMap
keys?Floats can be compared with the
==
,!=
,<
,<=
,>
, and>=
operators, and with thepartial_cmp()
function.==
and!=
are part of thePartialEq
trait, while<
,<=
,>
,>=
, andpartial_cmp()
are part of thePartialOrd
trait.Floats cannot be compared with the
cmp()
function, which is part of theOrd
trait, as there is no total ordering for floats. Furthermore, there is no total equality relation for floats, and so they also do not implement theEq
trait.There is no total ordering or equality on floats because the floating-point value
NaN
is not less than, greater than, or equal to any other floating-point value or itself.Because floats do not implement
Eq
orOrd
, they may not be used in types whose trait bounds require those traits, such asBTreeMap
orHashMap
. This is important because these types assume their keys provide a total ordering or total equality relation, and will malfunction otherwise.There is a crate that wraps
f32
andf64
to provideOrd
andEq
implementations, which may be useful in certain cases.如何在数字类型间进行转换?
There are two ways: the
as
keyword, which does simple casting for primitive types, and theInto
andFrom
traits, which are implemented for a number of type conversions (and which you can implement for your own types). TheInto
andFrom
traits are only implemented in cases where conversions are lossless, so for example,f64::from(0f32)
will compile whilef32::from(0f64)
will not. On the other hand,as
will convert between any two primitive types, truncating values as necessary.Why doesn't Rust have increment and decrement operators?
Preincrement and postincrement (and the decrement equivalents), while convenient, are also fairly complex. They require knowledge of evaluation order, and often lead to subtle bugs and undefined behavior in C and C++.
x = x + 1
orx += 1
is only slightly longer, but unambiguous.字符串
How can I convert a
String
orVec<T>
to a slice (&str
and&[T]
)?Usually, you can pass a reference to a
String
orVec<T>
wherever a slice is expected. Using Deref coercions,String
s andVec
s will automatically coerce to their respective slices when passed by reference with&
or& mut
.Methods implemented on
&str
and&[T]
can be accessed directly onString
andVec<T>
. For example,some_string.char_at(0)
will work even thoughchar_at
is a method on&str
andsome_string
is aString
.In some cases, such as generic code, it's necessary to convert manually. Manual conversions can be achieved using the slicing operator, like so:
&my_vec[..]
.How can I convert from
&str
toString
or the other way around?The
to_string()
method converts from a&str
into aString
, andString
s are automatically converted into&str
when you borrow a reference to them. Both are demonstrated in the following example:两种不同的字符串类型有什么区别?
String
is an owned buffer of UTF-8 bytes allocated on the heap. MutableString
s can be modified, growing their capacity as needed.&str
is a fixed-capacity "view" into aString
allocated elsewhere, commonly on the heap, in the case of slices dereferenced fromString
s, or in static memory, in the case of string literals.&str
is a primitive type implemented by the Rust language, whileString
is implemented in the standard library.如何以 O(1) 复杂度访问一个
String
中的字符?并不能这样做。At least not without a firm understanding of what you mean by "character", and preprocessing the string to find the index of the desired character.
Rust strings are UTF-8 encoded. A single visual character in UTF-8 is not necessarily a single byte as it would be in an ASCII-encoded string. Each byte is called a "code unit" (in UTF-16, code units are 2 bytes; in UTF-32 they are 4 bytes). "Code points" are composed of one or more code units, and combine in "grapheme clusters" which most closely approximate characters.
Thus, even though you may index on bytes in a UTF-8 string, you can't access the
i
th code point or grapheme cluster in constant time. However, if you know at which byte that desired code point or grapheme cluster begins, then you can access it in constant time. Functions includingstr::find()
and regex matches return byte indices, facilitating this sort of access.为什么字符串默认为 UTF-8?
The
str
type is UTF-8 because we observe more text in the wild in this encoding – particularly in network transmissions, which are endian-agnostic – and we think it's best that the default treatment of I/O not involve having to recode codepoints in each direction.This does mean that locating a particular Unicode codepoint inside a string is an O(n) operation, although if the starting byte index is already known then they can be accessed in O(1) as expected. On the one hand, this is clearly undesirable; on the other hand, this problem is full of trade-offs and we'd like to point out a few important qualifications:
Scanning a
str
for ASCII-range codepoints can still be done safely byte-at-a-time. If you use.as_bytes()
, pulling out au8
costs onlyO(1)
and produces a value that can be cast and compared to an ASCII-rangechar
. So if you're (say) line-breaking on'\n'
, byte-based treatment still works. UTF-8 was well-designed this way.Most "character oriented" operations on text only work under very restricted language assumptions such as "ASCII-range codepoints only". Outside ASCII-range, you tend to have to use a complex (non-constant-time) algorithm for determining linguistic-unit (glyph, word, paragraph) boundaries anyway. We recommend using an "honest" linguistically-aware, Unicode-approved algorithm.
The
char
type is UTF-32. If you are sure you need to do a codepoint-at-a-time algorithm, it's trivial to write atype wstr = [char]
, and unpack astr
into it in a single pass, then work with thewstr
. In other words: the fact that the language is not "decoding to UTF32 by default" shouldn't stop you from decoding (or re-encoding any other way) if you need to work with that encoding.For a more in-depth explanation of why UTF-8 is usually preferable over UTF-16 or UTF-32, read the UTF-8 Everywhere manifesto.
我应该使用哪种字符串类型?
Rust has four pairs of string types, each serving a distinct purpose. In each pair, there is an "owned" string type, and a "slice" string type. The organization looks like this:
str
String
OsStr
OsString
CStr
CString
Path
PathBuf
Rust 的不同字符串类型适用于不同的目的。
String
和str
为 UTF-8 编码的通用目的字符串。OsString
和OsStr
的编码取决于当前平台,在与操作系统交互时 使用。CString
和CStr
是 Rust 中与 C 字符串相当的存在,用于 FFI 代码。PathBuf
和Path
是OsString
和OsStr
的方便包装,提供特定路径操作的方法。怎样写一个同时接受
&str
与String
的函数?有几种方法,具体取决于该函数的需求:
Into<String>
bound.AsRef<str>
bound.Cow<str>
as the input type.使用
Into<String>
在此例中,the function will accept both owned strings and string slices, either doing nothing or converting the input into an owned string within the function body. Note that the conversion needs to be done explicitly, and will not happen otherwise.
使用
AsRef<str>
在此例中,the function will accept both owned strings and string slices, either doing nothing or converting the input into a string slice. This can be done automatically by taking the input by reference, like so:
使用
Cow<str>
在此例中,该函数采用一个
Cow<str>
,它不是通用类型,而是一个容器,包含所需的字符串或字符串切片。集合
可以在 Rust 中有效地实现向量和链表的数据结构吗?
If your reason for implementing these data structures is to use them for other programs, there's no need, as efficient implementations of these data structures are provided by the standard library.
If, however, your reason is simply to learn, then you will likely need to dip into unsafe code. While these data structures can be implemented entirely in safe Rust, the performance is likely to be worse than it would be with the use of unsafe code. The simple reason for this is that data structures like vectors and linked lists rely on pointer and memory operations that are disallowed in safe Rust.
例如,a doubly-linked list requires that there be two mutable references to each node, but this violates Rust's mutable reference aliasing rules. You can solve this using
Weak<T>
, but the performance will be poorer than you likely want. With unsafe code you can bypass the mutable reference aliasing rule restriction, but must manually verify that your code introduces no memory safety violations.如何在不移动 / 消费的情况下迭代集合?
The easiest way is by using the collection's
IntoIterator
implementation. Here is an example for&Vec
:Rust
for
循环调用into_iter()
(defined on theIntoIterator
trait) for whatever they're iterating over. Anything implementing theIntoIterator
trait may be looped over with afor
loop.IntoIterator
is implemented for&Vec
and&mut Vec
, causing the iterator frominto_iter()
to borrow the contents of the collection, rather than moving/consuming them. The same is true for other standard collections as well.如果需要移动 / 消费迭代器,在迭代中撰写没有
&
或&mut
的for
。如果您需要直接访问一个借用的迭代器,您通常可以调用
iter()
方法来获取它。为什么需要在数组声明中键入数组大小?
You don't necessarily have to. If you're declaring an array directly, the size is inferred based on the number of elements. But if you're declaring a function that takes a fixed-size array, the compiler has to know how big that array will be.
One thing to note is that currently Rust doesn't offer generics over arrays of different size. If you'd like to accept a contiguous container of a variable number of values, use a
Vec
or slice (depending on whether you need ownership).Ownership
如何实现包含循环的图或其他数据结构?
There are at least four options (discussed at length in Too Many Linked Lists):
Rc
和Weak
来允许节点共享所有权以实现它。虽然这种方法要支付内存管理的成本。unsafe
code using raw pointers. This will be more efficient, but bypasses Rust's safety guarantees.UnsafeCell
. There are explanations and code available for this approach.我该如何定义一个包含对其自身字段之一引用的结构体?
这有可能,但没有用。结构本身是永久借用,因此无法移动。下面用一些代码说明:
按值传递,消费,移动和转让所有权之间有什么区别?
These are different terms for the same thing. In all cases, it means the value has been moved to another owner, and moved out of the possession of the original owner, who can no longer use it. If a type implements the
Copy
trait, the original owner's value won't be invalidated, and can still be used.为什么某些类型的值在传递到一个函数后可以使用,而另一些类型的值传递后使用会导致错误?
如果一个类型实现了
Copy
特征,则它将在传递到一个函数时被复制。Rust 中的所有数字类型都实现了Copy
,但结构类型默认并未实现Copy
,因此它们是被移动。这意味着结构体不能在其他地方重用,除非它是通过 return 从函数中移出。如何处理 “use of moved value” 错误?
This error means that the value you're trying to use has been moved to a new owner. The first thing to check is whether the move in question was necessary: if it moved into a function, it may be possible to rewrite the function to use a reference, rather than moving. Otherwise if the type being moved implements
Clone
, then callingclone()
on it before moving will move a copy of it, leaving the original still available for further use. Note though that cloning a value should typically be the last resort since cloning can be expensive, causing further allocations.If the moved value is of your own custom type, consider implementing
Copy
(for implicit copying, rather than moving) orClone
(explicit copying).Copy
is most commonly implemented with#[derive(Copy, Clone)]
(Copy
requiresClone
), andClone
with#[derive(Clone)]
.If none of these are possible, you may want to modify the function that acquired ownership to return ownership of the value when the function exits.
在方法声明中使用
self
、&self
与&mut self
的规则是什么?self
,当一个函数需要消费该值&self
,当一个函数只需要该值的只读引用&mut self
,当一个函数不需消费而要变更该值我该怎么能理解借用检查器?
借用检查器只使用几条规则,而这可以在 Rust 之书的借用部分找到,当评估(Eval) Rust 代码时。这些规则为:
While the rules themselves are simple, following them consistently is not, particularly for those unaccustomed to reasoning about lifetimes and ownership.
The first step in understanding the borrow checker is reading the errors it produces. A lot of work has been put into making sure the borrow checker provides quality assistance in resolving the issues it identifies. When you encounter a borrow checker problem, the first step is to slowly and carefully read the error reported, and to only approach the code after you understand the error being described.
The second step is to become familiar with the ownership and mutability-related container types provided by the Rust standard library, including
Cell
,RefCell
, andCow
. These are useful and necessary tools for expressing certain ownership and mutability situations, and have been written to be of minimal performance cost.The single most important part of understanding the borrow checker is practice. Rust's strong static analyses guarantees are strict and quite different from what many programmers have worked with before. It will take some time to become completely comfortable with everything.
If you find yourself struggling with the borrow checker, or running out of patience, always feel free to reach out to the Rust community for help.
Rc
在什么时候有用?This is covered in the official documentation for
Rc
, Rust's non-atomically reference-counted pointer type. In short,Rc
and its thread-safe cousinArc
are useful to express shared ownership, and have the system automatically deallocate the associated memory when no one has access to it.如何从函数返回一个闭包?
To return a closure from a function, it must be a "move closure", meaning that the closure is declared with the
move
keyword. As explained in the Rust book, this gives the closure its own copy of the captured variables, independent of its parent stack frame. Otherwise, returning a closure would be unsafe, as it would allow access to variables that are no longer valid; put another way: it would allow reading potentially invalid memory. The closure must also be wrapped in aBox
, so that it is allocated on the heap. Read more about this in the book.什么是强制退还,它是如何工作?
A deref coercion is a handy coercion that automatically converts references to pointers (e.g.,
&Rc<T>
or&Box<T>
) into references to their contents (e.g.,&T
). Deref coercions exist to make using Rust more ergonomic, and are implemented via theDeref
trait.A Deref implementation indicates that the implementing type may be converted into a target by a call to the
deref
method, which takes an immutable reference to the calling type and returns a reference (of the same lifetime) to the target type. The*
prefix operator is shorthand for thederef
method.They're called"coercions" because of the following rule, quoted here from the Rust book:
For example, if you have a
&Rc<String>
, it will coerce via this rule into a&String
, which then coerces to a&str
in the same way. So if a function takes a&str
parameter, you can pass in a&Rc<String>
directly, with all coercions handled automatically via theDeref
trait.The most common sorts of deref coercions are:
&Rc<T>
to&T
&Box<T>
to&T
&Arc<T>
to&T
&Vec<T>
to&[T]
&String
to&str
生命周期
为什么有生命周期?
Lifetimes are Rust's answer to the question of memory safety. They allow Rust to ensure memory safety without the performance costs of garbage collection. They are based on a variety of academic work, which can be found in the Rust book.
为什么生命周期语法是这样的?
The
'a
syntax comes from the ML family of programming languages, where'a
is used to indicate a generic type parameter. For Rust, the syntax had to be something that was unambiguous, noticeable, and fit nicely in a type declaration right alongside traits and references. Alternative syntaxes have been discussed, but no alternative syntax has been demonstrated to be clearly better.如何将一个函数中创建的东西返回为借用?
You need to ensure that the borrowed item will outlive the function. This can be done by binding the output lifetime to some input lifetime like so:
An alternative is to eliminate the references entirely by returning an owning type like
String
:这种方法更简单,但往往导致不必要的分配。
为什么某些引用有生命周期,比如
&'a T
,而某些没有,比如&T
?In fact, all reference types have a lifetime, but most of the time you do not have to write it explicitly. The rules are as follows:
struct
orenum
definition, all lifetimes must be explicitly declared.If these rules result in compilation errors, the Rust compiler will provide an error message indicating the error caused, and suggesting a potential solution based on which step of the inference process caused the error.
Rust 如何保证 “无空指针” 和“无悬挂指针”?
The only way to construct a value of type
&Foo
or&mut Foo
is to specify an existing value of typeFoo
that the reference points to. The reference "borrows" the original value for a given region of code (the lifetime of the reference), and the value being borrowed from cannot be moved or destroyed for the duration of the borrow.如果没有
null
,我该怎么表达缺失值?You can do that with the
Option
type, which can either beSome(T)
orNone
.Some(T)
indicates that a value of typeT
is contained within, whileNone
indicates the absence of a value.泛型
什么是 “单态化”?
Monomorphisation specializes each use of a generic function (or structure) with specific instance, based on the parameter types of calls to that function (or uses of the structure).
During monomorphisation a new copy of the generic function is translated for each unique set of types the function is instantiated with. This is the same strategy used by C++. It results in fast code that is specialized for every call-site and statically dispatched, with the tradeoff that functions instantiated with many different types can cause "code bloat", where multiple function instances result in larger binaries than would be created with other translation strategies.
Functions that accept trait objects instead of type parameters do not undergo monomorphisation. Instead, methods on the trait objects are dispatched dynamically at runtime.
一个函数和一个没有捕获任何变量的闭包有什么区别?
Functions and closures are operationally equivalent, but have different runtime representations due to their differing implementations.
Functions are a built-in primitive of the language, while closures are essentially syntactic sugar for one of three traits:
Fn
,FnMut
, andFnOnce
. When you make a closure, the Rust compiler automatically creates a struct implementing the appropriate trait of those three and containing the captured environment variables as members, and makes it so the struct can be called as a function. Bare functions can not capture an environment.The big difference between these traits is how they take the
self
parameter.Fn
takes&self
,FnMut
takes&mut self
, andFnOnce
takesself
.Even if a closure does not capture any environment variables, it is represented at runtime as two pointers, the same as any other closure.
什么是更高级的类型,为什么我想要它们,为什么 Rust 没有它们?
Higher-kinded types are types with unfilled parameters. Type constructors, like
Vec
,Result
, andHashMap
are all examples of higher-kinded types: each requires some additional type parameters in order to actually denote a specific type, likeVec<u32>
. Support for higher-kinded types means these "incomplete" types may be used anywhere "complete" types can be used, including as generics for functions.Any complete type, like
i32
,bool
, orchar
is of kind*
(this notation comes from the field of type theory). A type with one parameter, likeVec<T>
is of kind* -> *
, meaning thatVec<T>
takes in a complete type likei32
and returns a complete typeVec<i32>
. A type with three parameters, likeHashMap<K, V, S>
is of kind* -> * -> * -> *
, and takes in three complete types (likei32
,String
, andRandomState
) to produce a new complete typeHashMap<i32, String, RandomState>
.In addition to these examples, type constructors can take lifetime arguments, which we'll denote as
Lt
. For example,slice::Iter
has kindLt -> * -> *
, because it must be instantiated likeIter<'a, u32>
.The lack of support for higher-kinded types makes it difficult to write certain kinds of generic code. It's particularly problematic for abstracting over concepts like iterators, since iterators are often parameterized over a lifetime at least. That in turn has prevented the creation of traits abstracting over Rust's collections.
Another common example is concepts like functors or monads, both of which are type constructors, rather than single types.
Rust doesn't currently have support for higher-kinded types because it hasn't been a priority compared to other improvements we want to make. Since the design is a major, cross-cutting change, we also want to approach it carefully. But there's no inherent reason for the current lack of support.
通用类型中
<T=Foo>
这样的命名类型参数是什么意思?These are called associated types, and they allow for the expression of trait bounds that can't be expressed with a
where
clause. For example, a generic boundX: Bar<T=Foo>
means "X
must implement the traitBar
, and in that implementation ofBar
,X
must chooseFoo
forBar
's associated type,T
." Examples of where such a constraint cannot be expressed via awhere
clause include trait objects likeBox<Bar<T=Foo>>
.Associated types exist because generics often involve families of types, where one type determines all of the others in a family. For example, a trait for graphs might have as its
Self
type the graph itself, and have associated types for nodes and for edges. Each graph type uniquely determines the associated types. Using associated types makes it much more concise to work with these families of types, and also provides better type inference in many cases.我可以重载运算符吗?哪些可以,怎么做?
You can provide custom implementations for a variety of operators using their associated traits:
Add
for+
,Mul
for*
, and so on. It looks like this:The following operators can be overloaded:
+
Add
+=
AddAssign
binary -
Sub
-=
SubAssign
*
Mul
*=
MulAssign
/
Div
/=
DivAssign
unary -
Neg
%
Rem
%=
RemAssign
&
BitAnd
BitOr
BitOrAssign
^
BitXor
^=
BitXorAssign
!
Not
<<
Shl
<<=
ShlAssign
>>
Shr
>>=
ShrAssign
*
Deref
mut *
DerefMut
[]
Index
mut []
IndexMut
Why the split between
Eq
/PartialEq
andOrd
/PartialOrd
?There are some types in Rust whose values are only partially ordered, or have only partial equality. Partial ordering means that there may be values of the given type that are neither less than nor greater than each other. Partial equality means that there may be values of the given type that are not equal to themselves.
Floating point types (
f32
andf64
) are good examples of each. Any floating point type may have the valueNaN
(meaning "not a number").NaN
is not equal to itself (NaN == NaN
is false), and not less than or greater than any other floating point value. As such, bothf32
andf64
implementPartialOrd
andPartialEq
but notOrd
and notEq
.As explained in the earlier question on floats, these distinctions are important because some collections rely on total orderings/equality in order to give correct results.
输入 / 输出
如何将文件读入一个
String
?Using the
read_to_string()
method, which is defined on theRead
trait instd::io
.如何有效地读取文件输入?
The
File
type implements theRead
trait, which has a variety of functions for reading and writing data, includingread()
,read_to_end()
,bytes()
,chars()
, andtake()
. Each of these functions reads a certain amount of input from a given file.read()
reads as much input as the underlying system will provide in a single call.read_to_end()
reads the entire buffer into a vector, allocating as much space as is needed.bytes()
andchars()
allow you to iterate over the bytes and characters of the file, respectively. Finally,take()
allows you to read up to an arbitrary number of bytes from the file. Collectively, these should allow you to efficiently read in any data you need.For buffered reads, use the
BufReader
struct, which helps to reduce the number of system calls when reading.Rust 中如何进行异步输入 / 输出?
There are several libraries providing asynchronous input / output in Rust, including mioco, coio-rs, and rotor.
如何在 Rust 中获取命令行参数?
The easiest way is to use
Args
, which provides an iterator over the input arguments.If you're looking for something more powerful, there are a number of options on crates.io.
错误处理
为什么 Rust 没有异常?
Exceptions complicate understanding of control-flow, they express validity/invalidity outside of the type system, and they interoperate poorly with multithreaded code (a major focus of Rust).
Rust prefers a type-based approach to error handling, which is covered at length in the book. This fits more nicely with Rust's control flow, concurrency, and everything else.
What's the deal with
unwrap()
everywhere?unwrap()
is a function that extracts the value inside anOption
orResult
and panics if no value is present.unwrap()
shouldn't be your default way to handle errors you expect to arise, such as incorrect user input. In production code, it should be treated like an assertion that the value is non-empty, which will crash the program if violated.It's also useful for quick prototypes where you don't want to handle an error yet, or blog posts where error handling would distract from the main point.
为什么我尝试运行使用
try!
宏的示例代码时收到错误?It's probably an issue with the function's return type. The
try!
macro either extracts the value from aResult
, or returns early with the errorResult
is carrying. This means thattry
only works for functions that returnResult
themselves, where theErr
-constructed type implementsFrom::from(err)
. In particular, this means that thetry!
macro cannot work inside themain
function.Is there an easier way to do error handling than having
Result
s everywhere?If you're looking for a way to avoid handling
Result
s in other people's code, there's alwaysunwrap()
, but it's probably not what you want.Result
is an indicator that some computation may or may not complete successfully. Requiring you to handle these failures explicitly is one of the ways that Rust encourages robustness. Rust provides tools like thetry!
macro to make handling failures ergonomic.If you really don't want to handle an error, use
unwrap()
, but know that doing so means that the code panics on failure, which usually results in a shutting down the process.并发
Can I use static values across threads without an
unsafe
block?Mutation is safe if it's synchronized. Mutating a static
Mutex
(lazily initialized via the lazy-static crate) does not require anunsafe
block, nor does mutating a staticAtomicUsize
(which can be initialized without lazy_static).More generally, if a type implements
Sync
and does not implementDrop
, it can be used in astatic
.宏
Can I write a macro to generate identifiers?
Not currently. Rust macros are "hygienic macros", which intentionally avoid capturing or creating identifiers that may cause unexpected collisions with other identifiers. Their capabilities are significantly different than the style of macros commonly associated with the C preprocessor. Macro invocations can only appear in places where they are explicitly supported: items, method declarations, statements, expressions, and patterns. Here, "method declarations" means a blank space where a method can be put. They can't be used to complete a partial method declaration. By the same logic, they can't be used to complete a partial variable declaration.
调试和工具
如何调试 Rust 程序?
Rust programs can be debugged using gdb or lldb, the same as C and C++. In fact, every Rust installation comes with one or both of rust-gdb and rust-lldb (depending on platform support). These are wrappers over gdb and lldb with Rust pretty-printing enabled.
rustc
said a panic occurred in standard library code. How do I locate the mistake in my code?This error is usually caused by
unwrap()
ing aNone
orErr
in client code. Enabling backtraces by setting the environment variableRUST_BACKTRACE=1
helps with getting more information. Compiling in debug mode (the default forcargo build
) is also helpful. Using a debugger like the providedrust-gdb
orrust-lldb
is also helpful.我该使用什么 IDE?
Rust 有许多开发环境可供选择,详见官方的 IDE 支持页面。
gofmt
is great. Where'srustfmt
?rustfmt
is right here, and is being actively developed to make reading Rust code as easy and predictable as possible.底层
如何
memcpy
字节?如果您想安全地克隆现有切片,可以使用
clone_from_slice
。要复制可能重叠的字节,使用
copy
. 要复制非重叠字节,使用copy_nonoverlapping
。两个特性都是unsafe
,因此两者都可以颠覆本语言的安全保障,使用时请小心。没有标准库的 Rust 还有适当的功能吗?
绝对可以。Rust 程序可以使用
#![no_std]
属性设置为不加载标准库。使用此属性集后,您可以继续使用 Rust 核心库,那些是与平台无关的原函数。也因此,它不包括 IO、并发、堆分配等。我可以用 Rust 写一个操作系统吗?
行的!事实上,有多个项目正在进行中。
如何以大端或小端格式读取与写入一个文件或其他字节流中的是
i32
或f64
这样的数字类型?您应该检出 byteorder 包装箱,它提供了这种实用程序。
Rust 是否保证特定的数据布局?
默认不会。在一般情况下,
enum
和struct
布局是未定义的。这允许编译器进行潜在优化,例如重新使用 padding for the discriminant, compacting variants of nestedenum
s, reordering fields to remove padding, etc.enums
which carry no data ("C-like") are eligible to have a defined representation. Suchenums
are easily distinguished in that they are simply a list of names that carry no data:#[repr(C)]
属性可以应用到诸如enums
以提供等同 C 语言中的表示。This allows using Rustenum
s in FFI code where Cenum
s are also used, for most use cases. The attribute can also be applied tostruct
s to get the same layout as a Cstruct
would.跨平台
在 Rust 中表达特定平台行为的惯用方式是什么?
特定平台的行为可以使用条件编译属性,诸如
target_os
、target_family
、target_endian
等。Rust 可以用于 Android/iOS 编程吗?
可以!已经有为 Android 和 iOS 使用 Rust 的例子。它需要一些工作来完成设置,但是 Rust 在这两个平台上都能正常工作。
我可以在网页浏览器中运行我的 Rust 程序吗?
目前还不行,但也正在努力使 Rust 能用 Emscripten 编译为 Web 版。
如何在 Rust 中交叉编译?
Rust 的交叉编译是可能的,它但需要一些流程完成设置。每个 Rust 编译器都是一个交叉编译器,但库需要针对目标平台进行交叉编译。
Rust 确实为每个受支持平台方法标准库副本,在分发目录上可以找到各构建目录,其中包含的
rust-std-*
文件就是它们,但尚没有自动安装它们的方法。模块和包装箱
模块与包装箱之间的关系是什么?
为什么 Rust 编译器找不到我
use
的库?There are a number of possible answers, but a common mistake is not realizing that
use
declarations are relative to the crate root. Try rewriting your declarations to use the paths they would use if defined in the root file of your project and see if that fixes the problem.There are also
self
andsuper
, which disambiguateuse
paths as being relative to the current module or parent module, respectively.For complete information on
use
ing libraries, read the Rust book's chapter "Crates and Modules".为什么我必须在包装箱顶层用
mod
声明模块文件,而不能直接use
它们?有两种方法在 Rust 中声明模块:内链或者另一个文件。下面是两个例子:
在第一个例子中,模块在其使用的同一个文件中定义。第二个例子中,主文件中的模块声明告诉编译器找到
hello.rs
或hello/mod.rs
,然后加载该文件。注意
mod
与use
的差异:mod
声明一个模块的存在,而use
引用一个在别处声明的模块,将其内容纳入到当前模块的范围。如何配置 Cargo 使用代理服务器?
正如 Cargo 的配置文档所说,您可以设置配置文件中
[http]
下的 “proxy” 变量使 Cargo 使用一个代理服务器。为什么编译器找不到方法实现,即使我已经
use
包装箱?For methods defined on a trait, you have to explicitly import the trait declaration. This means it's not enough to import a module where a struct implements the trait, you must also import the trait itself.
为什么编译器不能为我推断
use
声明?It probably could, but you also don't want it to. While in many cases it is likely that the compiler could determine the correct module to import by simply looking for where a given identifier is defined, this may not be the case in general. Any decision rule in
rustc
for choosing between competing options would likely cause surprise and confusion in some cases, and Rust prefers to be explicit about where names are coming from.For example, the compiler could say that in the case of competing identifier definitions the definition from the earliest imported module is chosen. So if both module
foo
and modulebar
define the identifierbaz
, butfoo
is the first registered module, the compiler would insertuse foo::baz;
.If you know this is going to happen, perhaps it saves a small number of keystrokes, but it also greatly increases the possibility for surprising error messages when you actually meant for
baz()
to bebar::baz()
, and it decreases the readability of the code by making the meaning of a function call dependent on module declaration. These are not tradeoffs we are willing to make.但在未来,IDE 可以帮助管理声明,这会在两方面带来更好的结果:机器人帮忙拉取名称,并明确声明这些名称来自何处。
如何动态加载 Rust 库?
在 Rust 中使用 libloading 导入动态库,它提供了一个用于动态链接的跨平台系统。
为什么 crates.io 没有名字空间?
Quoting the official explanation of https://crates.io's design:
库
我该怎样发出一个 HTTP 请求?
标准库不包含 HTTP 的实现,因此您需要用外部的包装箱。 Hyper 是最流行的选择,但也有众多其他可选。
如何用 Rust 编写 GUI 应用程序?
有多种方法在 Rust 中编写 GUI 应用程序。查阅我们的GUI 框架列表吧。
如何解析 JSON/XML?
Serde 是推荐的将 Rust 数据序列化与反序列化为多种格式的库。
有标准的 2D+ 矢量和形状包装箱吗?
还没有呢!你能写一个吗?
如何用 Rust 编写 OpenGL 应用程序?
Glium is the major library for OpenGL programming in Rust. GLFW is also a solid option.
我能用 Rust 编写一个电子游戏吗?
没问题!Rust 主要的游戏编程库是 Piston,另有 Rust 游戏编程的 reddit 板块 和 IRC 频道(Mozilla IRC 上的
#rust-gamedev
)。设计模式
Rust 是面向对象吗?
它是多范式。你在面向对象(OO)语言中做的许多事情也能在 Rust 中做,但并不是所有都可以,并且不一定是你熟悉的方式。
如何将面向对象的概念映射到 Rust?
That depends. There are ways of translating object-oriented concepts like multiple inheritance to Rust, but as Rust is not object-oriented the result of the translation may look substantially different from its appearance in an OO language.
如何处理有可选参数的结构体的配置?
The easiest way is to use the
Option
type in whatever function you're using to construct instances of the struct (usuallynew()
). Another way is to use the builder pattern, where only certain functions instantiating member variables must be called before the construction of the built type.如何在 Rust 中做全局对象?
Rust 中可以用
const
声明在编译时计算的全局常量,而static
可用于可变的全局变量。请注意,修改一个static mut
变量需要使用unsafe
,因为它允许数据竞争(races), one of the things guaranteed not to happen in safe Rust.const
与static
值的一项重要区别是,你可以引用static
值,但不能引用const
值,因为它没有一个特定的内存位置。有关const
与static
的更多信息,请阅读 Rust 之书。如何设置程序定义的编译时的常量?
Rust 目前对编译时常量的支持有限。您可以使用
const
声明(类似static
,但它不可变,并且在内存中没有特定位置)定义原函数,以及定义const
函数和固有方法。To define procedural constants that can't be defined via these mechanisms, use the
lazy-static
crate, which emulates compile-time evaluation by automatically evaluating the constant at first use.我可以在 main 发生前运行初始化代码吗?
Rust 没有 “
main
之前” 的概念。你想找到的最接近的可能是lazy-static
包装箱,它在初次使用时通过懒惰初始化静态变量做到类似 “在 main 之前”。Rust 是否允许全局的非常量表达式值?
No. Globals cannot have a non-constant-expression constructor and cannot have a destructor at all. Static constructors are undesirable because portably ensuring a static initialization order is difficult. Life before main is often considered a misfeature, so Rust does not allow it.
See the C++ FQA about the "static initialization order fiasco", and Eric Lippert's blog for the challenges in C#, which also has this feature.
您可以以 lazy-static 包装箱使用近似的非常量表达式全局对象。
其他语言
如何在 Rust 中实现类似 C 的
struct X {static int X;};
?如上所说,Rust 没有
static
字段。不过,您可以在指定的模块中声明一个static
变量,这样它将为该模块私有。如何将 C 风格的枚举转换为整数,反之亦然?
Converting a C-style enum to an integer can be done with an
as
expression, likee as i64
(wheree
is some enum).Converting in the other direction can be done with a
match
statement, which maps different numeric values to different potential values for the enum.为什么 Rust 程序使用比 C 更多的内存?
There are several factors that contribute to Rust programs having, by default, larger binary sizes than functionally-equivalent C programs. In general, Rust's preference is to optimize for the performance of real-world programs, not the size of small programs.
Monomorphization
Rust monomorphizes generics, meaning that a new version of a generic function or type is generated for each concrete type it's used with in the program. This is similar to how templates work in C++. For example, in the following program:
Two distinct versions of
foo
will be in the final binary, one specialized to ani32
input, one specialized to a&str
input. This enables efficient static dispatch of the generic function, but at the cost of a larger binary.调试符号
Rust programs compile with some debug symbols retained, even when compiling in release mode. These are used for providing backtraces on panics, and can be removed with
strip
, or another debug symbol removal tool. It is also useful to note that compiling in release mode with Cargo is equivalent to setting optimization level 3 with rustc. An alternative optimization level (calleds
orz
) has recently landed and tells the compiler to optimize for size rather than performance.Jemalloc
Rust uses jemalloc as the default allocator, which adds some size to compiled Rust binaries. Jemalloc is chosen because it is a consistent, quality allocator that has preferable performance characteristics compared to a number of common system-provided allocators. There is work being done to make it easier to use custom allocators, but that work is not yet finished.
链接时优化
Rust does not do link-time optimization by default, but can be instructed to do so. This increases the amount of optimization that the Rust compiler can potentially do, and can have a small effect on binary size. This effect is likely larger in combination with the previously mentioned size optimizing mode.
标准库
The Rust standard library includes libbacktrace and libunwind, which may be undesirable in some programs. Using
#![no_std]
can thus result in smaller binaries, but will also usually result in substantial changes to the sort of Rust code you're writing. Note that using Rust without the standard library is often functionally closer to the equivalent C code.作为一个例子,下列 C 程序读入一个名称,并向该名称的人说 “Hello”。
将这用 Rust 重写,您可能得到这样的内容:
This program, when compiled and compared against the C program, will have a larger binary and use more memory. But this program is not exactly equivalent to the above C code. The equivalent Rust code would instead look something like this:
Which should indeed roughly match C in memory usage, at the expense of more programmer complexity, and a lack of static guarantees usually provided by Rust (avoided here with the use of
unsafe
).为什么 Rust 没有像 C 一样稳定的 ABI,以及为什么必须用 extern 来标注?
Committing to an ABI is a big decision that can limit potentially advantageous language changes in the future. Given that Rust only hit 1.0 in May of 2015, it is still too early to make a commitment as big as a stable ABI. This does not mean that one won't happen in the future, though. (Though C++ has managed to go for many years without specifying a stable ABI.)
The
extern
keyword allows Rust to use specific ABI's, such as the well-defined C ABI, for interop with other languages.Rust 代码能调用 C 代码吗?
可以。从 Rust 调用 C 代码与从 C++ 调用 C 代码一样高效。
C 代码能调用 Rust 代码吗?
可以。The Rust code has to be exposed via an
extern
declaration, which makes it C-ABI compatible. Such a function can be passed to C code as a function pointer or, if given the#[no_mangle]
attribute to disable symbol mangling, can be called directly from C code.我已经写了完美的 C++。 Rust 能给我什么?
Modern C++ includes many features that make writing safe and correct code less error-prone, but it's not perfect, and it's still easy to introduce unsafety. This is something the C++ core developers are working to overcome, but C++ is limited by a long history that predates a lot of the ideas they are now trying to implement.
Rust was designed from day one to be a safe systems programming language, which means it's not limited by historic design decisions that make getting safety right in C++ so complicated. In C++, safety is achieved by careful personal discipline, and is very easy to get wrong. In Rust, safety is the default. It gives you the ability to work in a team that includes people less perfect than you are, without having to spend your time double-checking their code for safety bugs.
如何在 Rust 中做到 C++ 模板专业化 那样?
Rust 目前还没有完全等同的模板专业化,这正在研究并有希望尽快加入。不过,可以用关联类型达成类似的效果。
Rust 的所有权系统如何与 C++ 中的语义相关联?
The underlying concepts are similar, but the two systems work very differently in practice. In both systems, "moving" a value is a way to transfer ownership of its underlying resources. For example, moving a string would transfer the string's buffer rather than copying it.
In Rust, ownership transfer is the default behavior. For example, if I write a function that takes a
String
as argument, this function will take ownership of theString
value supplied by its caller:As you can see in the snippet above, in the function
caller
, the first call toprocess
transfers ownership of the variables
. The compiler tracks ownership, so the second call toprocess
results in an error, because it is illegal to give away ownership of the same value twice. Rust will also prevent you from moving a value if there is an outstanding reference into that value.C++ takes a different approach. In C++, the default is to copy a value (to invoke the copy constructor, more specifically). However, callees can declare their arguments using an "rvalue reference", like
string&&
, to indicate that they will take ownership of some of the resources owned by that argument (in this case, the string's internal buffer). The caller then must either pass a temporary expression or make an explicit move usingstd::move
. The rough equivalent to the functionprocess
above, then, would be:C++ 编译器 are not obligated to track moves. For example, the code above compiles without a warning or error, at least using the default settings on clang. Moreover, in C++ ownership of the string
s
itself (if not its internal buffer) remains withcaller
, and so the destructor fors
will run whencaller
returns, even though it has been moved (in Rust, in contrast, moved values are dropped only by their new owners).如何让 Rust 与 C++ 互操作,反之亦然?
Rust 与 C++ 可以通过 C 互操作。Rust 和 C++ 都提供一个适用于 C 的外部函数接口,我们可以用它来相互沟通。如果编写 C 语言的绑定太麻烦,您随时可以用 rust-bindgen 来帮忙自动生成可执行的 C 绑定。
Rust 有 C++ 风格的构造函数吗?
没有。Functions serve the same purpose as constructors without adding language complexity. The usual name for the constructor-equivalent function in Rust is
new()
, although this is just a convention rather than a language rule. Thenew()
function in fact is just like any other function. An example of it looks like so:Rust 有复制构造函数吗?
不太完全。Types which implement
Copy
will do a standard C-like "shallow copy" with no extra work (similar to "plain old data" in C++). It is impossible to implementCopy
types that require custom copy behavior. Instead, in Rust "copy constructors" are created by implementing theClone
trait, and explicitly calling theclone
method. Making user-defined copy operators explicit surfaces the underlying complexity, making it easier for the developer to identify potentially expensive operations.Rust 有移动构造函数吗?
没有。Values of all types are moved via
memcpy
. This makes writing generic unsafe code much simpler since assignment, passing and returning are known to never have a side effect like unwinding.Go 与 Rust 有何相似,及它们有什么不同?
Rust 与 Go 有着完全不同的设计目标。下列差异不是全部的差异(它们太多而无法列出),而是较重要的一些:
Rust 特征与 Haskell 类型类相较如何?
Rust traits are similar to Haskell typeclasses, but are currently not as powerful, as Rust cannot express higher-kinded types. Rust's associated types are equivalent to Haskell type families.
Some specific difference between Haskell typeclasses and Rust traits include:
Self
.trait Bar
in Rust corresponds toclass Bar self
in Haskell, andtrait Bar<Foo>
in Rust corresponds toclass Bar foo self
in Haskell.trait Sub: Super
, compared toclass Super self => Sub self
in Haskell.impl
resolution considers the relevantwhere
clauses and trait bounds when deciding whether twoimpl
s overlap, or choosing between potentialimpl
s. Haskell only considers the constraints in theinstance
declaration, disregarding any constraints provided elsewhere.ExistentialQuantification
.文档
为什么 Stack Overflow(问答网站)上的许多 Rust 回答是错的?
Rust 语言已存在多年,而它在 2015 年 5 月才达成 1.0 版本。在那达成之前,语言曾经发生过很多变化,而问答网站上的答案可能只适用较旧的语言版本。
随着时间的推移,会有越来越多的答案适用于当前版本,从而改善这一问题。
我该在哪报告 Rust 文档的问题?
您可以在 Rust 编译器的问题跟踪器中报告 Rust 文档的问题。在报告前,请先阅读贡献指南。
如何查看我的项目所依赖的库的 rustdoc 文档?
When you use
cargo doc
to generate documentation for your own project, it also generates docs for the active dependency versions. These are put into thetarget/doc
directory of your project. Usecargo doc --open
to open the docs after building them, or just open uptarget/doc/index.html
yourself.https://github.com/jethrogb/rust-www/blob/master/zh-CN/faq.md
The text was updated successfully, but these errors were encountered: