С возвращением, коллеги-разработчики C++! Мы рады представить вторую часть нашей серии статей о Rust, продолжая наше путешествие в увлекательный мир программирования на Rust.

В предыдущей статье мы углубились в нюансы управления памятью и обработки ошибок, проливая свет на сильные стороны Rust в этих областях. Теперь, в этой новой части, мы рассмотрим некоторые из более тонких синтаксических различий и углубимся в модель параллелизма Rust.

С точки зрения синтаксиса мы раскроем ключевые отличия Rust от C++. Понимание этих различий не только облегчит плавный переход на Rust, но и улучшит читаемость кода и удобство сопровождения.

Кроме того, в модели параллелизма Rust мы обсудим, как система владения Rust и философия «бесстрашного параллелизма» дают нам возможность писать параллельный код с беспрецедентной уверенностью, обнаруживая при этом потенциальные проблемы во время самой компиляции.

Синтаксис

По мере того, как мы углубляемся в различия между синтаксисом Rust и C++, мы разделим наше обсуждение на два основных раздела. В первом разделе мы рассмотрим незначительные изменения, влияющие на то, как мы пишем программы. К ним относятся различия в объявлении функций, переменных и структур данных, которые могут повлиять на внешний вид и структуру кода.

Во втором разделе мы сосредоточимся на изменениях, которые оказывают более глубокое влияние на способ написания программ и языковые парадигмы, которые они воплощают. Это будет включать в себя важные аспекты, такие как управление пакетами и библиотеку String. Понимание этих различий не только поможет нам ориентироваться при переходе между языками, но и вызовет изменение мышления, что приведет к более оптимальному использованию языка Rust.

Тонкие изменения

Тонкие изменения могут быть возобновлены в изменениях в объявлении и способе использования некоторых языковых функций, эти изменения в основном сохраняют парадигмы из языка c++:

Объявление переменной. В случае объявления переменной в синтаксисе C++: переменные объявляются с определенным типом, например, int, float или double, за которым следует имя переменной, например «int age”inПеременные Rust объявляются с использованием ключевого слова let, за которым следует имя и тип переменной, например, “let age: i32”.

Функции. В C++ функции определяются с использованием типа возвращаемого значения, за которым следует синтаксис function_name() с аргументами в скобках, например «int factorial(int number) { code }» в Rust: функции определяются с использованием синтаксиса fn имя_функции(), за которым следует тип возвращаемого значения, обозначенный с помощью «-›», например «fn factorial(number: i32) -> i32 { code }».

Поток управления: C++ и Rust используют одни и те же операторы потока управления, такие как if, else, for и while, следуют знакомому синтаксису большинства языков программирования, самая большая разница заключается в том, что Rust использует блочный подход. и применяет строгие правила для выражений условий без круглых скобок.

Изменения парадигмы

Существенные изменения, которые мы здесь обсудим, проявляются тремя основными способами. Во-первых, парадигма неизменяемости, которая делает все по умолчанию постоянным, продвигая код, который является надежным, потокобезопасным и менее подверженным ошибкам. Во-вторых, мощный оператор «match» меняет то, как мы обрабатываем поток управления, обеспечивая исчерпывающее сопоставление с образцом и краткое выражение кода. Наконец, библиотека String предлагает свежий взгляд на обработку строк, демонстрируя акцент Rust на безопасности и владении.

Неизменность.Первое существенное изменение заключается в том, что любая переменная в Rust по умолчанию является неизменной. Такой подход делает упор на безопасность, предотвращает непреднамеренные мутации и делает код более надежным.

Оператор сопоставления.Оператор сопоставления, несомненно, является одной из самых замечательных функций Rust, служащей основой обработки ошибок, как мы обсуждали в нашей предыдущей статье. Этот оператор включает мощный механизм переключения поведения в зависимости от типа переменной, что позволяет нам обрабатывать различные сценарии.

Оператор match в Rust обеспечивает исчерпывающее сопоставление с образцом, что означает, что он обязывает нас обрабатывать все возможные результаты переключения или образца. Эта функция гарантирует, что все потенциальные случаи и пограничные случаи явно рассматриваются в нашем коде.

Другой пример использования оператора match — это возможность легко обрабатывать различные случаи и шаблоны, обеспечивая краткую и исчерпывающую обработку вариантов данных. Эта возможность не только упрощает обработку ошибок, но и оптимизирует работу с композициями и сложными структурами данных.

Библиотека строк. Действительно, понимание обработки строк в Rust и библиотеке String может стать серьезным сдвигом парадигмы для разработчиков, использующих другие языки. Это требует понимания фундаментальных понятий, таких как владение, заимствование и время жизни, которые являются неотъемлемой частью модели безопасности памяти и параллелизма Rust. Давайте кратко рассмотрим использование строк и библиотеки String в Rust.

Создание String: мы можем создать новый пустой String с помощью метода String::new() или преобразовать строковый литерал в String с помощью метода String::from().

let mut my_string = String::new(); 
my_string = String::from("Hello, Rust!");

Конкатенация строк: Rust предоставляет оператор + для объединения строк. Однако из-за передачи права собственности эта операция не очень эффективна для частых конкатенаций. Для повышения производительности используйте макрос format! или метод String::push_str().

let greeting = "Hello"; let name = "Rust"; 
let full_greeting = format!("{} {}", greeting, name); 
let greeting = "Hello"; let name = "Rust"; 
let full_greeting = format!("{} {}", greeting, name);  
let mut message = String::from("Hello"); 
message.push_str(", Rust!"); 
let mut message = String::from("Hello"); 
message.push_str(", Rust!");

Фрагменты строк: фрагменты строк (&str) позволяют заимствовать часть String, не вступая во владение. Они широко используются в аргументах функций, чтобы избежать ненужного копирования.

fn print_message(message: &str) {     
  println!("{}", message); 
} 

...

let my_string = String::from("Hello, Rust!"); 
print_message(&my_string[..5]); // Output: "Hello"

Библиотека Rust String уделяет особое внимание безопасному управлению памятью и контролю владения. Хотя поначалу это может показаться сложным для понимания, это ключ к раскрытию сильных сторон Rust в написании безопасного, высокопроизводительного и параллельного кода.

совпадение

Абсолютно! Подход Rust к параллелизму и безопасности — одна из его выдающихся особенностей, отличающая его от многих других языков программирования. Сочетание владения и заимствования обеспечивает безопасное параллельное программирование и устраняет распространенные ловушки, связанные с гонками данных и проблемами безопасности потоков. Вот как Rust достигает параллелизма и безопасности:

Владение:модель владения Rust гарантирует, что у каждой части данных есть один владелец в любой момент времени. Этот владелец имеет эксклюзивный доступ к данным, и когда владелец выходит за пределы области действия, Rust автоматически освобождает память. Такой подход устраняет необходимость в сборке мусора и значительно снижает риск утечек памяти. В параллельном слове это позволит, чтобы любые данные просто контролировались одним потоком в данный момент времени.

Заимствование. Чтобы несколько частей кода могли одновременно обращаться к данным и работать с ними, Rust использует заимствование. Заимствование позволяет функциям или блокам временно заимствовать ссылки на данные, не вступая во владение. Это предотвращает гонки данных и гарантирует, что данные остаются действительными на протяжении всего времени их использования.

Эти две функции гарантируют, что разработчики смогут уверенно писать параллельный и параллельный код, не беспокоясь об общих проблемах с многопоточностью. Rust позволяет программистам создавать высокопроизводительные параллельные приложения, которые одновременно безопасны и эффективны.

Используя возможности параллелизма Rust, разработчики могут в полной мере использовать преимущества современных многоядерных процессоров, сохраняя при этом уверенность в том, что их код свободен от гонок данных и ошибок, связанных с потоками.

каналы

Rust действительно позволяет разработчикам реализовывать широкий спектр параллельных и параллельных систем, используя его мощные языковые функции. Однако в некоторых исключительных случаях определенные варианты использования могут быть недостижимы исключительно за счет безопасных абстракций языка. Это может привести к тому, что разработчики будут рассматривать использование «небезопасного кода» в качестве крайней меры.

Небезопасный код в Rust позволяет обойти некоторые проверки и ограничения безопасности языка, но сопряжен со значительными рисками, поскольку язык крайне не поощряет его случайное использование. Прежде чем прибегать к небезопасному коду, я настоятельно рекомендую переоценить проблему и изучить альтернативные парадигмы, чтобы увидеть, возможно ли безопасное решение, одна из этих парадигм описана ниже, то есть сделать процесс разделения памяти с каналами.

Вот идея слогана из Документации по языку Go: Не общайтесь, делясь памятью; вместо этого делитесь памятью, общаясь. Книга по программированию на Rust

в Rust подход к совместному использованию памяти между несколькими потоками упрощается с помощью каналов, которые работают аналогично каналам в Go. Это обеспечивает безопасный и эффективный способ связи и передачи данных между параллельными потоками. Простой пример использования каналов в Rust приведен ниже в виде фрагмента кода:

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

Листинг 16–10 из книги ржавчины: создание канала и назначение двух половин tx и rx

Используя этот подход, становится удивительно просто распределять память между потоками, особенно при работе с данными через указатели. Это обеспечивает эффективную передачу прав собственности между потоками без дополнительных затрат на копирование всего объекта.

Заключение

Завершение второй статьи о синтаксисе и параллелизме Rust было большим путешествием. Пока мы готовимся к последнему выпуску, давайте подготовимся к изучению инструментов и экосистемы, которые делают Rust выдающимся языком для разработчиков.

В следующей статье мы рассмотрим надежный инструментарий Rust, который включает в себя менеджер пакетов, систему сборки и другие утилиты для разработки. Мы узнаем, как Cargo, менеджер пакетов Rust, упрощает управление зависимостями и обеспечивает бесшовную интеграцию с внешними библиотеками.

Кроме того, мы изучим возможности тестирования Rust и то, как он продвигает культуру написания надежного и поддерживаемого кода посредством автоматизированного тестирования. Система сборки Rust и возможность работы с различными средами разработки также будут в центре внимания.

Понимая инструменты и экосистему, которые предоставляет Rust, мы сможем лучше использовать весь потенциал языка, оптимизировать рабочий процесс разработки и предоставлять высококачественное, готовое к работе программное обеспечение.

Давайте с нетерпением ждем поучительного завершения нашей серии статей о Rust и следите за обновлениями, чтобы узнать больше о мощном мире инструментов и среды разработки Rust!