Введение: Что такое функции и зачем они нужны?
Представь, что ты пишешь инструкцию для робота, который должен убирать твою комнату. Если каждый раз описывать все действия заново — «возьми пылесос, включи его, проведи по ковру, выключи, поставь на место» — это будет очень долго и утомительно. Гораздо удобнее один раз написать команду «пропылесось ковёр», а потом просто вызывать её, когда нужно.
В программировании такие «команды» называются функциями. Функция — это именованный блок кода, который можно вызывать многократно. В Rust функции играют особенно важную роль, потому что они помогают делать код:
- Понятным — вместо кучи непонятных строк ты видишь осмысленные названия действий.
- Переиспользуемым — написал один раз, используй сколько хочешь.
- Легко изменяемым — нужно поменять логику? Исправляешь в одном месте, а не во всём коде.
На этом уроке мы научимся создавать свои функции, передавать им данные и получать результаты. Поехали!
1. Первая функция на Rust
Давай сразу напишем простую программу с функцией. Открой Rust Playground или создай новый проект через Cargo.
// Это наша первая функция!
fn privetstvie() {
println!("Привет, я функция на Rust!");
}
fn main() {
// Вызываем функцию по имени
privetstvie();
privetstvie(); // Можем вызвать сколько угодно раз!
}
Разберём по частям:
- fn — ключевое слово, которое говорит Rust: «сейчас будет функция».
- privetstvie — имя функции (давай на русском, чтобы было понятнее).
- () — круглые скобки. В них мы будем передавать данные в функцию, но пока они пустые.
- { ... } — тело функции, то есть код, который выполнится при вызове.
- В main мы просто пишем имя функции со скобками — это и есть вызов.
Заметь: Rust неважно, в каком порядке объявлены функции. Главное, чтобы они были видны в месте вызова. Обычно main пишут последней, но можно и первой.
2. Параметры: как передать данные в функцию
Функции были бы бесполезны, если бы они всегда делали одно и то же. Представь функцию «свари суп». Без параметров она будет варить всегда один и тот же суп. А с параметрами мы можем сказать: «свари суп из картошки», «свари суп из грибов».
В Rust параметры указываются внутри круглых скобок. Нужно написать имя параметра и его тип через двоеточие.
fn skazhi_privet(imya: &str) {
println!("Привет, {}!", imya);
}
fn main() {
skazhi_privet("Анна"); // Выведет: Привет, Анна!
skazhi_privet("Максим"); // Выведет: Привет, Максим!
}
Что здесь произошло?
- Мы объявили параметр
imyaтипа&str(строка). - Внутри функции используем
{}— это подстановка значения. - При вызове передаём аргумент — конкретное имя в кавычках.
Важно! Параметры — это переменные, которые существуют только внутри функции. Как только функция закончила работу, они исчезают.
Несколько параметров
Можно передавать сколько угодно параметров, разделяя их запятыми:
fn opisanie_zhivotnogo(vid: &str, vozrast: u32) {
println!("Это {}. Ему {} лет.", vid, vozrast);
}
fn main() {
opisanie_zhivotnogo("кот", 3);
opisanie_zhivotnogo("собака", 5);
}
Здесь vid — строка, а vozrast — целое число без знака (u32). Типы должны совпадать при вызове.
3. Возврат значения: как получить результат
Функции не только принимают данные, но и могут возвращать результаты. Например, функция «сложи два числа» должна вернуть сумму. В Rust для этого используется стрелка -> и тип возвращаемого значения.
fn slozhit(a: i32, b: i32) -> i32 {
let summa = a + b;
summa // Возвращаем значение (без точки с запятой!)
}
fn main() {
let resultat = slozhit(5, 3);
println!("5 + 3 = {}", resultat); // Выведет: 5 + 3 = 8
}
Обрати внимание на две вещи:
- После
->указан тип возвращаемого значения —i32(целое число со знаком). - В конце функции написано
summaбез точки с запятой. Это особенность Rust: последнее выражение в функции считается возвращаемым значением.
Можно также использовать ключевое слово return, но в Rust его обычно опускают:
fn umnozhit(x: i32, y: i32) -> i32 {
return x * y; // Тоже работает, но так пишут реже
}
Если функция ничего не возвращает, пишут -> () или просто опускают тип. На самом деле println! — это макрос, который возвращает пустой кортеж ().
Функция с условием
Давай сделаем функцию, которая определяет, можно ли купить билет в кино (возраст >= 12 лет):
fn mozhno_v_kino(vozrast: u32) -> bool {
if vozrast >= 12 {
true
} else {
false
}
}
fn main() {
let vozrast_petyi = 10;
if mozhno_v_kino(vozrast_petyi) {
println!("Петя, проходи!");
} else {
println!("Петя, подрасти ещё.");
}
}
Функция возвращает bool — true или false. Обрати внимание: внутри if мы пишем true и false без точки с запятой, потому что это возвращаемые значения.
4. Реальные аналогии из жизни
Чтобы лучше понять функции, представь, что твой мозг — это компьютер, а функции — это навыки, которые ты освоил.
- Функция без параметров: «поздороваться» — ты просто говоришь «Привет!», никаких дополнительных данных не нужно.
- Функция с параметрами: «приготовить бутерброд» — нужны ингредиенты (хлеб, сыр, колбаса). Ты передаёшь их, и получаешь готовый бутерброд.
- Функция с возвратом: «решить пример 2+2» — ты берёшь числа, складываешь и возвращаешь результат 4.
- Функция в функции: «приготовить завтрак» может вызывать «сварить яйцо», «поджарить тост» и «заварить чай».
В программировании это называется композиция функций — когда одна функция использует другие.
5. Пример посложнее: калькулятор
Давай напишем несколько функций для простого калькулятора:
fn slozhit(a: f64, b: f64) -> f64 {
a + b
}
fn vychit(a: f64, b: f64) -> f64 {
a - b
}
fn umnozhit(a: f64, b: f64) -> f64 {
a * b
}
fn delit(a: f64, b: f64) -> f64 {
if b == 0.0 {
println!("Ошибка: деление на ноль!");
0.0
} else {
a / b
}
}
fn main() {
let x = 10.0;
let y = 3.0;
println!("{} + {} = {}", x, y, slozhit(x, y));
println!("{} - {} = {}", x, y, vychit(x, y));
println!("{} * {} = {}", x, y, umnozhit(x, y));
println!("{} / {} = {}", x, y, delit(x, y));
}
Обрати внимание: мы используем f64 — числа с плавающей точкой (дробные). В функции delit мы проверяем, не ноль ли делитель, чтобы программа не упала.
Такой код легко читать: каждая функция делает только одно действие. Если нужно добавить новую операцию (например, возведение в степень), мы просто пишем ещё одну функцию.
6. Функции могут вызывать другие функции
Это очень мощная возможность. Давай напишем функцию, которая проверяет, является ли число чётным, и используем её в другой функции:
fn chetnoe(chislo: i32) -> bool {
chislo % 2 == 0
}
fn proverka_chisel(spisok: &[i32]) {
for &chislo in spisok {
if chetnoe(chislo) {
println!("{} — чётное", chislo);
} else {
println!("{} — нечётное", chislo);
}
}
}
fn main() {
let chisla = [1, 2, 3, 4, 5, 6];
proverka_chisel(&chisla);
}
Здесь proverka_chisel вызывает chetnoe для каждого числа. Это как шеф-повар, который просит помощника проверить яйца на свежесть.
7. Возврат нескольких значений с помощью кортежей
Иногда нужно вернуть из функции не одно, а несколько значений. В Rust для этого используют кортежи — это как маленькая коробочка, в которой лежит несколько вещей.
fn razdelit_chisla(spisok: &[i32]) -> (i32, i32) {
let mut summa_pol = 0;
let mut summa_otr = 0;
for &chislo in spisok {
if chislo > 0 {
summa_pol += chislo;
} else {
summa_otr += chislo;
}
}
(summa_pol, summa_otr) // Возвращаем кортеж из двух чисел
}
fn main() {
let dannye = [5, -3, 7, -1, 10, -4];
let (polozh, otric) = razdelit_chisla(&dannye);
println!("Сумма положительных: {}", polozh);
println!("Сумма отрицательных: {}", otric);
}
Тип возврата (i32, i32) — это кортеж из двух целых чисел. При вызове мы «распаковываем» его в две переменные: polozh и otric.
8. Область видимости переменных
Важное понятие: переменные, объявленные внутри функции, не видны снаружи. Это называется локальная область видимости.
fn test() {
let sekret = 42; // Эта переменная живёт только внутри test()
println!("Внутри функции: {}", sekret);
}
fn main() {
test();
// println!("{}", sekret); // ОШИБКА! sekret здесь не существует
}
Зачем это нужно? Чтобы разные функции не мешали друг другу. Ты можешь в одной функции использовать имя i для счётчика, а в другой — тоже i, и они не будут конфликтовать.
9. Константы и статические переменные
Иногда нужно, чтобы значение было доступно всем функциям. Для этого используют const (константа) или static (статическая переменная). Константы принято писать ЗАГЛАВНЫМИ_БУКВАМИ.
const MAKSIMALNAYA_SKOROST: u32 = 120;
fn proverit_skorost(skorost: u32) {
if skorost > MAKSIMALNAYA_SKOROST {
println!("Превышение! Максимум {}", MAKSIMALNAYA_SKOROST);
} else {
println("Скорость в норме.");
}
}
fn main() {
proverit_skorost(100);
proverit_skorost(150);
}
Константы должны иметь известный тип, и их нельзя изменить. Это как табличка с правилами — все видят, но менять нельзя.
10. Типичные ошибки новичков
Давай разберём ошибки, которые часто встречаются у начинающих:
Ошибка 1: Забыл тип параметра
// НЕПРАВИЛЬНО:
fn privet(imya) { // Rust не знает, какой тип у imya
println!("Привет, {}", imya);
}
// ПРАВИЛЬНО:
fn privet(imya: &str) {
println!("Привет, {}", imya);
}
Ошибка 2: Поставил точку с запятой после возвращаемого значения
// НЕПРАВИЛЬНО:
fn sum(a: i32, b: i32) -> i32 {
a + b; // Точка с запятой превращает это в оператор, а не возврат
}
// ПРАВИЛЬНО:
fn sum(a: i32, b: i32) -> i32 {
a + b // Без точки с запятой — это выражение, которое возвращается
}
Ошибка 3: Несоответствие типов
// НЕПРАВИЛЬНО:
fn get_number() -> i32 {
"пять" // Строка не может быть i32!
}
// ПРАВИЛЬНО:
fn get_number() -> i32 {
5 // Целое число
}
Практические задания
Теперь твоя очередь! Попробуй выполнить эти задания. Если что-то не получается — перечитай урок или напиши в комментариях.
Задание 1: Приветствие по времени суток
Напиши функцию privetstvie_po_vremeni, которая принимает час (u32 от 0 до 23) и возвращает строку с приветствием:
- 6-11: "Доброе утро!"
- 12-17: "Добрый день!"
- 18-23: "Добрый вечер!"
- 0-5: "Доброй ночи!"
Подсказка: используй if-else или match. Функция должна возвращать &str.
Задание 2: Калькулятор площади
Напиши три функции:
ploshad_kvadrata(storona: f64) -> f64— площадь квадрата (сторона * сторона).ploshad_pryamougolnika(shirina: f64, vysota: f64) -> f64— площадь прямоугольника.ploshad_kruga(radius: f64) -> f64— площадь круга (π * радиус^2). Используйstd::f64::consts::PIдля числа π.
В main вызови все три функции и выведи результаты.
Задание 3: Анализ оценок
Напиши функцию analiz_ocenok(ocenki: &[u32]) -> (u32, u32, f64), которая принимает срез оценок (целые числа от 1 до 5) и возвращает кортеж:
- Первое значение — максимальная оценка.
- Второе — минимальная оценка.
- Третье — средняя оценка (как дробное число).
Подсказка: для среднего нужно сумму всех оценок разделить на их количество. Используй ocenki.len() для длины. Не забудь преобразовать типы: сумма — u32, а среднее — f64, используй as f64.
Пример вызова:
let moy_ocenki = [4, 5, 3, 5, 4, 2];
let (max, min, sred) = analiz_ocenok(&moy_ocenki);
println!("Макс: {}, Мин: {}, Среднее: {:.2}", max, min, sred);
Удачи! Функции — это основа, на которой строится весь Rust. Освоив их, ты сможешь писать сложные программы, разбивая их на маленькие, понятные части.