This article will briefly describe what This Week in Rust is, tweet # 418, # 419 on Rust’s new RFC, and tweet # 417 on Rust Serde’s implementation of serialization.

RFC 2972: Constrained Naked Functions

Naked Functions can be defined as normal Functions, but are actually different from normal Functions. Bare functions do not stack, that is, unlike normal functions that store or restore registers when called or terminated, bare functions do not have a Prologue or Epilogue of the function, but rather resemble GOTO tags in C. If you write a return inside a bare function, this behavior is undefined, because unlike a normal function that reads the address stored in a register when it returns, a bare function without a prologue or epilogue reads the caller’s return address, and the return is not executed as expected.

Previously, Rust copied the way other compilers supported bare functions to implement this functionality in Rust, but bare functions are often not recommended because of their undefined behavior and complex management patterns. RFC 2972 attempts to better qualify and define bare functions in Rust so that their convenience can be exploited.

In Rust, naked functions can be represented by #[naked], and the function body should contain only assembly code. The detailed rules can be found in RFC 2972, outlined below:

  • In addition toextern 'Rust'Otherwise, the invocation rule must be declared
  • Only the FFI security parameters and return values should be specified
  • Unusable#[inline]or#[inline(*)]attribute
  • The function body contains only a singleasm! (a)And the expression:
    • In addition toconstorsymCannot include any other operators to avoid undefined behavior by using or referring to stack variables
    • Must containnoreturnoptions
    • In addition toatt_syntaxCannot contain other options
    • I have to make sure that the function isunsafeOr satisfy the call rule

The compiler will disallow unused variables and implicitly use the attribute #[inline(never)]. The following is an example:

const THREE: usize = 3;

#[naked]
pub extern "sysv64" fn add_n(number: usize) - >usize {
    unsafe{ asm! ("add rdi, {}"
            "mov rax, rdi"
            "ret".constTHREE, options(noreturn) ); }}Copy the code

In this example, the call specification is SYSV64, so the function input is in the RDI register and the function return is in the RAX register.

Still, there are flaws. This implementation is not compatible with the #[naked] property of the current nightly edition and also changes ASM! Parameter definition of (). Not as asm! The register of the () operator was originally defined to contain undefined values, but in the context of revised bare functions, this definition is updated to the initial register state that is not modified by function calls. Second, these definitions may be too strict compared to the original bare function definitions. Using Rust instead of assembly theory in a bare function is possible, but in practice very difficult. Finally, different architectures may have different support for bare functions.

RFC 3180: Cargo –crate-type CLI Argument

The Crate -type attribute defines the target Crate type to be generated by cargo Build. It can only be declared for libraries and examples. Binaries, Tests, and Benchmarks are always of the type “bin”.

The default types are as follows:

Target Crate Type
Normal library "lib"
Proc-macro library "proc-macro"
Example "bin"

The Linkage can be specified with declarable options. The summary is as follows:

  • bin: Executable file.
  • libRustc: RuSTC: ruSTC: RuSTC: RuSTC: RuSTC: RuSTC: RuSTC: RuSTC
  • rlibA Rust library file, similar to a static library but linked by the compiler in future compilations, is similar to a dynamic library.
  • dylib: dynamic library, which can be relied on as other libraries or executable files*.soAnd for macOS*.dylib, for Windows*.dll.
  • cdylib: dynamic system library, used when compiling libraries that need to be made available to calls in other languages. File type in Linux is*.soAnd for macOS*.dylib, for Windows*.dll.
  • staticlibThe compiler never attempts to link to a static library, which contains all native Crate code and all upstream dependencies. The file type is minGW on Linux, macOS, or Windows*.aIn Windows (MSVC)*.lib.
  • proc-macro: The output form is not described here, but is required-LPath, the compiler will assume that the output file is a macro that can be used by a program and is compiled depending on the current schema. Crate compiled with this option should only output procedure macros.

Cargo has been added to RFC 3180. –crate-type

can be entered as a command line parameter, which has the same function as crate-type defined in Cargo. That is, if a command line parameter definition conflicts with one defined in Cargo. Toml, Cargo will assume that the value is the same as the command line input.

Serialization and Serde

Serialization is the process of converting the state information of an object into a form that can be stored or transmitted. Deserialization is the reverse process. Serde is Rust’s serialization and deserialization framework for parsing Json, Yaml, Toml, Bson, and other serialized files. Serde_json implements SERIalization and deserialization of Json files based on this framework.

Serde

Serde implements serialization and deserialization through traits. If a data structure implements Serde’s Serialize and Deserialize traits, it can be serialized or deserialized.

Serialize

Serde’s Serialize trait is defined as follows:

pub trait Serialize {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer;
}
Copy the code

An instance that implements the Serialize trait can be serialized by calling the Serialize method and taking the corresponding instance that implements the Serialize trait as an argument. The Serializer trait is defined as follows:

pub trait Serializer: Sized {
    type Ok;
    type Error: Error;
    type SerializeSeq: SerializeSeq<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTuple: SerializeTuple<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTupleStruct: SerializeTupleStruct<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTupleVariant: SerializeTupleVariant<Ok = Self::Ok, Error = Self::Error>;
    type SerializeMap: SerializeMap<Ok = Self::Ok, Error = Self::Error>;
    type SerializeStruct: SerializeStruct<Ok = Self::Ok, Error = Self::Error>;
    type SerializeStructVariant: SerializeStructVariant<Ok = Self::Ok, Error = Self::Error>;
    
    // The method to be implemented
    fn serialize_bool(self, v: bool) - >Result<Self::Ok, Self::Error>;
    fn serialize_i8(self, v: i8) - >Result<Self::Ok, Self::Error>;
    fn serialize_i16(self, v: i16) - >Result<Self::Ok, Self::Error>;
    fn serialize_i32(self, v: i32) - >Result<Self::Ok, Self::Error>;
    fn serialize_i64(self, v: i64) - >Result<Self::Ok, Self::Error>;
    fn serialize_u8(self, v: u8) - >Result<Self::Ok, Self::Error>;
    fn serialize_u16(self, v: u16) - >Result<Self::Ok, Self::Error>;
    fn serialize_u32(self, v: u32) - >Result<Self::Ok, Self::Error>;
    fn serialize_u64(self, v: u64) - >Result<Self::Ok, Self::Error>;
    fn serialize_f32(self, v: f32) - >Result<Self::Ok, Self::Error>;
    fn serialize_f64(self, v: f64) - >Result<Self::Ok, Self::Error>;
    fn serialize_char(self, v: char) - >Result<Self::Ok, Self::Error>;
    fn serialize_str(self, v: &str) - >Result<Self::Ok, Self::Error>;
    fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error>;
    fn serialize_none(self) - >Result<Self::Ok, Self::Error>;
    fn serialize_some<T: ?Sized> (self, 
        value: &T
    ) -> Result<Self::Ok, Self::Error>
    where
        T: Serialize;
    fn serialize_unit(self) - >Result<Self::Ok, Self::Error>;
    fn serialize_unit_struct(
        self, 
        name: &'static str) - >Result<Self::Ok, Self::Error>;
    fn serialize_unit_variant(
        self, 
        name: &'static str, 
        variant_index: u32, 
        variant: &'static str) - >Result<Self::Ok, Self::Error>;
    fn serialize_newtype_struct<T: ?Sized> (self, 
        name: &'static str, 
        value: &T
    ) -> Result<Self::Ok, Self::Error>
    where
        T: Serialize;
    fn serialize_newtype_variant<T: ?Sized> (self, 
        name: &'static str, 
        variant_index: u32, 
        variant: &'static str, 
        value: &T
    ) -> Result<Self::Ok, Self::Error>
    where
        T: Serialize;
    fn serialize_seq(
        self, 
        len: Option<usize- > >)Result<Self::SerializeSeq, Self::Error>;
    fn serialize_tuple(
        self, 
        len: usize) - >Result<Self::SerializeTuple, Self::Error>;
    fn serialize_tuple_struct(
        self, 
        name: &'static str, 
        len: usize) - >Result<Self::SerializeTupleStruct, Self::Error>;
    fn serialize_tuple_variant(
        self, 
        name: &'static str, 
        variant_index: u32, 
        variant: &'static str, 
        len: usize) - >Result<Self::SerializeTupleVariant, Self::Error>;
    fn serialize_map(
        self, 
        len: Option<usize- > >)Result<Self::SerializeMap, Self::Error>;
    fn serialize_struct(
        self, 
        name: &'static str, 
        len: usize) - >Result<Self::SerializeStruct, Self::Error>;
    fn serialize_struct_variant(
        self, 
        name: &'static str, 
        variant_index: u32, 
        variant: &'static str, 
        len: usize) - >Result<Self::SerializeStructVariant, Self::Error>;
    
    // Implemented method
    fn serialize_i128(self, v: i128) - >Result<Self::Ok, Self::Error> { ... }
    fn serialize_u128(self, v: u128) - >Result<Self::Ok, Self::Error> { ... }
    fn collect_seq<I>(self, iter: I) -> Result<Self::Ok, Self::Error>
    where
        I: IntoIterator,
        <I as IntoIterator>::Item: Serialize,
    { ... }
    fn collect_map<K, V, I>(self, iter: I) -> Result<Self::Ok, Self::Error>
    where
        K: Serialize,
        V: Serialize,
        I: IntoIterator<Item = (K, V)>,
    { ... }
    fn collect_str<T: ?Sized> (self, value: &T) -> Result<Self::Ok, Self::Error>
    where
        T: Display,
    { ... }
    fn is_human_readable(&self) - >bool{... }}Copy the code

Serde attempts to categorize all Rust data structures into one possible type using methods in this trait. When Serialize is implemented for a data type, you simply call the corresponding method. Such as:

impl<T> Serialize for Option<T>
where
    T: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            Some(ref value) => serializer.serialize_some(value),
            None => serializer.serialize_none(),
        }
    }
}
Copy the code

The Serialize trait is already implemented for most primitive types, as shown in “Module serde::ser”. The implementation of the Serializer trait is customized by third-party libraries or users, such as Serde_JSON.

serde_jsonAnd serialization

Serde_json serialization uses the following example:

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Address {
	street: String,
	city: String,}let address = Address {
	street: "10 Downing Street".to_owned(),
	city: "London".to_owned(),
};

// Serialize it to a JSON string.
letj = serde_json::to_string(&address)? ;// Print, write to a file, or send to an HTTP server.
println!("{}", j);
Copy the code

Or serialize a Json object, Value:

letJohn = json! ({"name": "John Doe"."age": 43."phones": [
		"+ 44 1234567"."+ 44 2345678"]});println!("first phone number: {}", john["phones"] [0]);

// Convert to a string of JSON and print it out
println!("{}", john.to_string());
Copy the code

Serde_json provides enumeration values and member types corresponding to Json, including Null, Bool, Number, String, Array, and Object. This converts the Json object (in serde_JSON, the Value instance) to a string. To_string is implemented via a Serializer object that implements the Ser ::Serializer trait and contains the same constructor as Writer and Formatter. The Display member FMT calls serialize on the Value object. The serialize method calls the structure object of the same name. This object implements ser::Serializer, which calls the Formatter method function in each serialization method to write a string to Writer. Formatter is responsible for writing formatting, such as match Boolean values and then printing the corresponding string “false” or “true”, and adding characters before and after each serialized string.

For an object that implements the Serialize trait, you can convert it into a Value object using the to_value method. The to_value will call value. Serialize, using serde_json:: Value ::Serializer. Serde_json /value/ser.rs defines Serializer, which converts the input into the corresponding value object based on Json object rules.

For custom structs, you can obtain this property with #[derive(Serialize)]. #[derive(Serialize)] process macro definition FN derive_serialize(input: TokenStream) in Serde /serde_derive Crate and relies on rust, proc-Macro2 and SYN. Proc-macro2 is a wrapper for the compiler Crate proc_macro and provides new interfaces and features such as implementing SYN and quote. Syn is a Rust source code parsing library that parses Rust code into an abstract syntax tree, while Quote provides the ability to convert an abstract syntax tree into Rust source code. The derive_serialize macro converts the input TokenStream displayed as syn::DeriveInput, which translates the target structure or enumeration recursion into a syntax tree and records its attributes, visibility, name, generics used, and data. Ser ::expand_derive_serialize takes this syntax tree as an argument and, after a series of filtering and reprocessing, implements the impL serde::Serialize procedure macro using the Quote library. Ser. rs provides functions such as serialize_enum, serialize_struct, serialize_tuple_struct, serialize_variant, etc. For different object types to achieve the corresponding IMPL source.

For example, for the custom structure Person, #[derive(Serialize)] expands as follows:

use serde::ser::{Serialize, SerializeStruct, Serializer};

struct Person {
	name: String,
    age: u8,
    phones: Vec<String>,}// This is what #[derive(Serialize)] would generate.
impl Serialize for Person {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
		where
			S: Serializer,
	{
		let mut s = serializer.serialize_struct("Person".3)? ; s.serialize_field("name", &self.name)?;
		s.serialize_field("age", &self.age)? ; s.serialize_field("phones", &self.phones)? ; s.end() } }Copy the code

Depending on the Serializer defined in serde_JSON, you can convert the Person instance into a corresponding Json object or Json format string.

In addition, serde_JSON provides the macro JSON! (), the token string in Json format can be converted into a Value instance, for example,

use serde_json::json;

letvalue = json! ({"code": 200."success": true."payload": {
		"features": [
			"serde"."json"]}});Copy the code

The macro parses the input into an abstract syntax tree and defines the internal macro jSON_internal, which treats different tree matches differently and generates corresponding Value instances.

Deserialize

Serde’s Deserialize trait is defined as follows:

pub trait Deserialize<'de> :Sized {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>;
}
Copy the code

Similarly, an instance that implements the Deserialize trait can call the method Deserialize to Deserialize, which restores the instance from data input in a certain format. The deserialize method takes an instance of the Deserializer trait as an argument. The Deserializer trait is defined as follows:

pub trait Deserializer<'de> :Sized {
    type Error: Error;
    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_byte_buf<V>(
        self, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_unit_struct<V>(
        self, 
        name: &'static str, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_newtype_struct<V>(
        self, 
        name: &'static str, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_tuple<V>(
        self, 
        len: usize, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_tuple_struct<V>(
        self, 
        name: &'static str, 
        len: usize, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_struct<V>(
        self, 
        name: &'static str, 
        fields: &'static [&'static str], 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_enum<V>(
        self, 
        name: &'static str, 
        variants: &'static [&'static str], 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_identifier<V>(
        self, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;
    fn deserialize_ignored_any<V>(
        self, 
        visitor: V
    ) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;

    fn deserialize_i128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de> {... }fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de> {... }fn is_human_readable(&self) - >bool{... }}Copy the code

For data input in a certain format, the Deserializer trait provides a way to convert the data into an instance of Rust. Like the Serialize trait, when Deserialize is implemented for a data type and you want to recover the data structure from the trait later, you simply call the corresponding method. Such as:

#[cfg(any(feature = "std", feature = "alloc")))
impl<'de> Deserialize<'de> for String {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_string(StringVisitor)
    }

    fn deserialize_in_place<D>(deserializer: D, place: &mut Self) - >Result<(), D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_string(StringInPlaceVisitor(place))
    }
}
Copy the code

Similarly, although different from the list of primitive types that implement the Serialize trait, the Deserialize trait also implements many primitive types, such as “Module serde::de”. The implementation of the Deserializer trait is also customized by third-party libraries or users.

serde_jsonAnd deserialization

Serde_json deserialization uses the following example:

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Person {
	name: String,
	age: u8,
	phones: Vec<String>,}// Some JSON input data as a &str. Maybe this comes from the user.
let data = r#" { "name": "John Doe", "age": 43, "phones": [ "+44 1234567", "+44 2345678" ] }"#;

// Parse the string of data into serde_json::Value.
letv: Value = serde_json::from_str(data)? ;// Parse the string of data into a Person object. This is exactly the
// same function as the one that produced serde_json::Value above, but
// now we are asking it for a Person as output.
letp: Person = serde_json::from_str(data)? ;// Do things just like with any other Rust data structure.
println!("Please call {} at the number {}", p.name, p.phones[0]);
Copy the code

From_str is defined as follows:

fn from_trait<'de, R, T>(read: R) -> Result<T>
where
    R: Read<'de>,
    T: de::Deserialize<'de{>,let mut de = Deserializer::new(read);
    letvalue = tri! (de::Deserialize::deserialize(&mut de));

    // Make sure the whole stream has been consumed.tri! (de.end());Ok(value)
}
Copy the code

From_str indirect invocation of the specified instance DE: : Deserialize: : Deserialize (& mut DE) method, the instance type is determined by the type of a variable assignment. Deserializer is responsible for converting Json data into Value objects, implementing the de::Deserializer trait, and implementing the parsing method. For example, for a given input, fn deserialize_any

(self, visitor: V) peek the first character and enter the selection branch. For Boolean values match as follows:

b't'= > {self.eat_char(); tri! (self.parse_ident(b"rue"));
    visitor.visit_bool(true)}b'f'= > {self.eat_char(); tri! (self.parse_ident(b"alse"));
    visitor.visit_bool(false)}Copy the code

tri! Implementing the equivalent of Result::map, parse_ident checks the input verbatim for expected values, such as Boolean values of “true” or “false”. The visitor instance implements the Serde ::de::Visitor trait, which indicates whether a given input meets the expected format. In the Case of Boolean value matching, the visit_bool method returns the corresponding value or error, depending on the given visitor instance rule.

For example the Value, DE: : Deserialize: : Deserialize (deserializer: Serde ::Deserializer<‘de>) defines ValueVisitor, implements each method of serde::de::Visitor trait, and specifies that when the given input meets the Json format, the corresponding value is returned. Otherwise, an error is reported. Deserialize_any (ValueVisitor) is then called to convert the Json string into the corresponding Value instance.

The process macro #[derive(Deserialize)] is implemented in a similar way to #[derive(Serialize)], which parses the source code of the target structure into a syntax tree followed by a call to de::expand_derive_deserialize. This method code logic and SER ::expand_derive_serialize consistent, and also in de. Rs to provide deserialize_enum, deserialize_struct, deserialize_tuple and other functions to achieve the IMPL source code. These functions also use the Quote library to realize the Visitor instance and pass it to deserializer. Deserialize_any, thus realizing the Deserialize trait.