[Learn Rust together | Design Patterns] Idiomatic Grammar - Using Borrowed Types as Parameters, Formatting and Concatenating Strings, and Constructors

Tip: After the article is written, the table of contents can be automatically generated. How to generate it can refer to the help document on the right


foreword

Rust is not a traditional object-oriented programming language, and all of its features make it unique. Therefore, learning Rust-specific design patterns is necessary. This series of articles is the author's study notes for learning "Rust Design Patterns" and his own insights. Therefore, the structure of this series of articles is also the same as that of this book (the structure may be adjusted later), basically divided into three parts

  1. idiomatic grammar
  2. Design Patterns
  3. Anti-patterns (anti_patterns)

Idioms are common styles, guidelines, and patterns for programming in Rust that are widely agreed upon by the community. Writing idiomatic code allows other developers to better understand the code you write.

This article is the beginning of this series of articles, starting from Rust's customary programming techniques to get started, corresponding to the book on design patterns, that is, the theme of this issue is

  1. 使用借用类型作为参数
  2. 格式化字符串
  3. 构造函数

1. Use borrowed types as parameters

Using a borrowed type as a parameter can avoid the indirection layer in the process of function parameter passing. For example, String has one indirection layer, &String has two indirection layers. We can avoid this situation by using &str, and let &String be called when the function Cast to &str.

In this example, we use &String as a function parameter to compare with &str.

This also applies to &Vec<T>with or&[T] with .&Box<T>&T

Borrowing an example from the book, for example, if we want to determine whether there are three vowels in a word, we use &String as a parameter to pass in, the code should be written as follows

Original sound: aeiou

fn three_vowels(word: &String) -> bool {
    
    
    let mut vowel_count = 0;
    for c in word.chars() {
    
    
        match c {
    
    
            'a' | 'e' | 'i' | 'o' | 'u' => {
    
    
                vowel_count += 1;
                if vowel_count >= 3 {
    
    
                    return true
                }
            }
            _ => vowel_count = 0
        }
    }
    false
}

fn main() {
    
    
    let ferris = "Ferris".to_string();
    let curious = "Curious".to_string();
    println!("{}: {}", ferris, three_vowels(&ferris));
    println!("{}: {}", curious, three_vowels(&curious));

    // 上面这么写是没有问题的,但是如果写成下面注释里面的样子,就会报错
    // println!("Ferris: {}", three_vowels("Ferris"));
    // println!("Curious: {}", three_vowels("Curious"));

}

This situation is also very common in C++, because the parameter type passed in the function signature should be &String. If "Ferris" is directly passed in, it is &str. When it is passed into the function, it will not be forced to convert to &String, friends who learn C++ must be very familiar with this point, if you want to make it possible to pass parameters like that, then the function signature has to be changed like this

fn three_vowels(word: &str) -> bool {
    
    

In this way, no matter what kind of parameter we pass, the normal result will be output

Ferris: false
Curious: true

Now we try such an example, given a sentence, to determine whether each word has three connected vowels, for this purpose, we change the code to the following

fn three_vowels(word: &str) -> bool {
    
    
    let mut vowel_count = 0;
    for c in word.chars() {
    
    
        match c {
    
    
            'a' | 'e' | 'i' | 'o' | 'u' => {
    
    
                vowel_count += 1;
                if vowel_count >= 3 {
    
    
                    return true
                }
            }
            _ => vowel_count = 0
        }
    }
    false
}

fn main() {
    
    
    let sentence_string =
        "Once upon a time, there was a friendly curious crab named Ferris".to_string();
    for word in sentence_string.split(' ') {
    
    
        if three_vowels(word) {
    
    
            println!("{} 有三个连续的元音!", word);
        }
    }
}

It can be seen that we have not changed the three_vowels function, but at the place where the function is called, use the split method to split the string into multiple words, and pass them into the function in turn. At this time, the benefits of &str are reflected, and the value returned by split The elements in the array are all of type &str. It is very simple to convert String type to &str.

2. Format splicing string

Generally speaking, when we are developing, we will use the push and push_str methods of the String method for string splicing, or directly use + to achieve string splicing, but sometimes it may be more convenient to use format!, especially A string of mixed types of stuff.

First we manually construct a string

let mut hello = "Hello ".to_owned();
hello.push_str(name);
hello.push('!');

This is a fairly common usage, now we use formatting to build this string

fn say_hello(name: &str) -> String {
    
    
    format!("Hello {}!", name)
}

format! returns a formatted string, {} is used as a placeholder, and then the parameters are passed in later, and the processed string can be returned. It is difficult to see its benefits in such a simple place, but when inserted When there are many things, you will know the benefits of formatting strings.

This piece has similar things in both python and js. I think it is easy to use in python and js. In js, only
let hello = `hello ${name}`

In Rust, using format! to format concatenated strings is the most concise and readable way.

Third, use the constructor

In Rust, there is actually no concept of a constructor, but a conventional associated function new can be used to create an object. like

pub struct Second {
    
    
    value: u64
}

impl Second {
    
    
    // 构建一个Second实例
    // 注意,这是个关联函数,参数里面没有self
    pub fn new(value: u64) -> Self {
    
    
        Self {
    
     value }
    }

    /// 返回秒数,即Second的value
    pub fn value(&self) -> u64 {
    
    
        self.value
    }
}

We created a Second structure and implemented the new method to construct a new object.

When in use, we can create an object by calling the new method

let s = Second::new(42);

The Default trait can also be implemented in Rust to implement the default constructor

impl Default for Second {
    
    
    fn default() -> Self {
    
    
        Self {
    
     value: 0 }
    }
}

Using derive to derive Default can also achieve the same effect, for example, you can write

#[derive(Default)]
pub struct Second {
    
    
    value: u64
}

impl Second {
    
    
    pub fn value(&self) -> u64 {
    
    
        self.value
    }
}

At this point we don't need the new method, we can directly call the default method to build the instance

let s = Second::default();

注意:It is recommended that you use new to create the constructor. In community development, new is usually used to implement the constructor, because it is more reasonable than default and more in line with reading habits. Its function is the same as default.


Summarize

This section is the first issue of the Rust design pattern. It mainly introduces some idiomatic syntax of the Rust design pattern.

  • Use borrowed types as function arguments
  • format concatenated string
  • Constructor

I believe that through this series of articles, you can have a deeper understanding of Rust.


I created 一起学Rusta community, and welcome friends who are interested in rust to join .

 http://t.csdn.cn/AsEZ9 

Guess you like

Origin blog.csdn.net/weixin_47754149/article/details/126571097