В Rust часто встречаются функции, принимающие &str
в качестве параметра.
fn foo(bar: &str) {
println!("{}", bar);
}
При вызове таких функций совершенно нормально передавать String
в качестве аргумента, ссылаясь на него.
let bar = String::from("bar");
foo(&bar);
Строго говоря, переданный аргумент — это &String
, а функция ожидает &str
, но Rust (как и следовало ожидать) просто это понимает, и все работает нормально. Однако это не относится к операторам match. Если я попытаюсь использовать ту же переменную bar
, что и раньше, в операторе match, наивное использование не будет скомпилировано:
match &bar {
"foo" => println!("foo"),
"bar" => println!("bar"),
_ => println!("Something else")
};
Rustc жалуется, что ожидал &str
, а получил &String
. И проблема, и решение очень очевидны: просто заимствуйте bar
более явно с .as_str()
. Но это подводит меня к главному вопросу: почему это так?
Если Rust может понять, что &String
тривиально преобразуется в &str
в случае аргументов функции, почему он не может сделать то же самое с операторами match? Является ли это результатом ограничения системы типов, или скрытая небезопасность в причудливых заимствованиях с операторами соответствия? Или это просто случай, когда улучшение качества жизни интегрируется в одних местах, но не в других? Я уверен, что у кого-то, кто разбирается в системах типов, есть ответ, но, похоже, в Интернете очень мало информации об этой маленькой причуде поведения.
Техническая причина, по которой это не работает, заключается в том, что match
проверка не является сайтом принуждения. Аргументы функции, как показано в вашем примере foo(&bar)
, являются возможными сайтами принуждения; и это позволяет вам передать &String
как &str
из-за принуждения Deref
.
Возможная причина, по которой это не сайт принуждения, заключается в том, что нет четкого типа, к которому его следует принуждать. В вашем примере вы бы хотели, чтобы это было &str
, поскольку это соответствует строковым литералам, но как насчет:
match &string_to_inspect {
"special" => println!("this is a special string"),
other => println!("this is a string with capacity: {}", other.capacity()),
};
Хотелось бы, чтобы match
действовал как &str
, чтобы соответствовать буквальному, но поскольку совпадение находится на &String
, можно было бы ожидать, что other
также будет &String
. Как удовлетворить обоих? Следующим логическим шагом было бы принудительное принудительное выполнение каждого шаблона, что было очень желательно... но это открывает целую банку червей, поскольку Deref
определяется пользователем. См. шаблоны deref от команды Rust lang для получения дополнительной информации.
https://doc.rust-lang.org/reference/type-coercions.html говорит:
Сайты принуждения
Приведение может происходить только в определенных местах приведения в программе; обычно это места, где желаемый тип является явным или может быть получен путем распространения из явных типов (без вывода типа). Возможные сайты принуждения:
[...]
Аргументы для вызовов функций
Приводимое значение является фактическим параметром и приводится к типу формального параметра.
но не match
проверяющий.
Типы принуждения
Принуждение разрешено между следующими типами:
- [...]
&T
или&mut T
на&U
, еслиT
реализуетDeref<Target = U>
.
Это называется «приведением к удалению ссылок» —
&String
может быть разыменован в&str
, и компилятор автоматически сделает это за вас, когда ожидается&str
. Фактическая проблема, которую вы описываете, вероятно, является просто ограничением компилятора.