Правило сироты
Что мешает пользователям писать произвольные реализации трейтов для любого типа?
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
// Крейт `postgresql-bindings`
pub struct PostgresqlConn(/* подробности */);
// Крейт `database-traits`, зависит от `postgresql-bindings`
pub trait DbConnection {
/* методы */
}
impl DbConnection for PostgresqlConn {} // ✅, `DbConnection` локальный.
// Крейт `mycoolnewdb` зависит от `database-traits`
pub struct MyCoolNewDbConn(/* подробности */);
impl DbConnection for MyCoolNewDbConn {} // ✅, `MyCoolNewDbConn` локальный.
// Ни `PostgresqlConn`, ни `DbConnection` не являются локальными для `mycoolnewdb`.
// Это привело бы к двум реализациям `DbConnection` для `PostgresqlConn`!
impl DbConnection for PostgresqlConn {} // ❌🔨
-
Трейт в экосистеме Rust никогда не должен реализовываться дважды. Две реализации одного трейта для одного типа — это конфликт, который не имеет решения.
-
Внутри крейта это можно предотвратить, обнаруживая множественные определения и запрещая их, но что насчёт разных крейтов во всей экосистеме Rust?
-
Типы либо локальны для крейта (определены в нём), либо нет.
В примере «крейтов»
PostgresqlConnлокален дляpostgresql-bindings, аMyCoolNewDbConnлокален дляmycoolnewdb. -
Трейты тоже либо локальны для крейта, либо нет.
И снова в примере трейт
DbConnectionлокален дляdatabase-traits. -
Если что-то локально, для него можно писать реализации трейтов.
Если трейт локален, его реализации можно писать для любого типа.
Если тип локален, для него можно писать реализации любых трейтов.
-
За пределами этих границ реализации трейтов написать нельзя.
Это сохраняет реализации «согласованными»: на весь набор крейтов может существовать только одна реализация трейта для типа.
Справка:
- https://doc.rust-lang.org/stable/reference/items/implementations.html#r-items.impl.trait.orphan-rule