LINUX.ORG.RU

Rust 1.12

 


1

6

Команда разработчиков Rust рада представить релиз Rust 1.12 — системного языка программирования, нацеленного на безопасную работу с памятью, скорость и параллельное выполнение кода. В этот релиз вошёл 1361 патч.

Новое в 1.12

По словам разработчиков, релиз 1.12 является, возможно, самым значительным с момента выпуска релиза 1.0. Самое заметное для пользователей изменение в версии 1.12 — это новый формат ошибок, выдаваемых rustc. Сообществом была проделана многочасовая работа по переводу вывода ошибок на новый формат. Кроме того, для лучшей интеграции и взаимодействия со средой разработки и другим инструментарием ошибки теперь можно выводить в формате JSON при помощи специального флага --error-format=json.

Самое большое внутреннее изменение — это переход на новый формат внутреннего представления программы MIR. Незаметное сегодня, это изменение открывает путь к череде будущих оптимизаций компилятора, и для некоторых кодовых баз оно уже показывает улучшения времени компиляции и уменьшение размера кода. Переход на MIR открывает ранее сложнодоступные возможности анализа и оптимизации. Первое из многочисленных грядущих изменений — переписывание прохода, генерирующего промежуточное представление LLVM, того, что rustc называет «трансляцией». После многомесячных усилий новый бэкенд, основанный на MIR, доказал, что готов к реальной работе. MIR содержит точную информацию о потоке управления программы, поэтому компилятор точно знает, перемещены типы или нет. Это значит, что он статически получает информацию о том, нужно ли выполнять деструктор значения. В случаях, когда значение может быть перемещено или не перемещено в конце области действия, компилятор просто использует флаг из одного бита на стеке, что, в свою очередь, проще для оптимизации проходов в LLVM. Конечный результат — уменьшенный объем работы компилятора и менее раздутый код во время исполнения.

Другие улучшения:

  • Множество мелких улучшений документации.
  • rustc теперь поддерживает три новые цели MUSL на платформе ARM: arm-unknown-linux-musleabi, arm-unknown-linux-musleabihf и armv7-unknown-linux-musleabihf. Эти цели поддерживают статически скомпонованные бинарные файлы. Однако, в собранном виде они пока не распространяются.
  • Повышена читабельность описаний ошибок в ссылках и неизвестных числовых типах.
  • Компилятор теперь может быть собран с LLVM 3.9.
  • Тестовые бинарные файлы теперь поддерживают аргумент --test-threads для указания количества потоков для запуска тестов, который действует точно так же, как переменная окружения RUST_TEST_THREADS.
  • В случае продолжительности выполнения тестов больше минуты показывается предупреждение.
  • Вместе с выпусками Rust теперь доступны пакеты с исходными кодами, которые можно установить при помощи rustup через команду % rustup component add rust-src. Исходные коды могут быть использованы для интеграции и взаимодействия со средой разработки и другим инструментарием.
  • Ускорено обновление индекса реестра.
  • cargo new получил флаг --lib.
  • Добавлен вывод профиля сборки (release/debug) после компиляции.
  • cargo publish получил флаг --dry-run.
  • Сокеты на Linux в подпроцессах теперь закрываются правильно через вызов SOCK_CLOEXEC.
  • Определения Unicode обновлены до 9.0.

Стабилизация библиотек:

  • Cell::as_ptr и RefCell::as_ptr.
  • IpAddr, Ivp4Addr и Ipv6Addr получили несколько новых методов.
  • LinkedList и VecDeque теперь имеют новый метод contains.
  • iter::Product и iter::Sum.
  • Option реализует From для содержащегося в нём типа.
  • Cell, RefCell и UnsafeCell реализует From для содержащихся в них типах.
  • Cow<str> реализует FromIterator для char, &str и String.
  • String реализует AddAssign.

Возможности Cargo

Самая большая возможность, добавленная в Cargo в этом цикле — «рабочие области». Описанные в RFC 1525, рабочие области позволяют группе пакетов разделять один общий файл Cargo.lock. Это позволяет намного легче придерживаться единственной версии общих зависимостей при наличии у вас проекта, который делится на несколько пакетов. Для включения этой возможности в большинстве мультипакетных проектов достаточно добавить одну единственную строчку [workspace] в Cargo.toml верхнего уровня, более сложным установкам может потребоваться дополнительная настройка.

Другая существенная возможность — переопределение источника пакета. При помощи инструментов cargo-vendor и cargo-local-registry можно переопределять зависимости локально (vendoring). Со временем это станет фундаментом для построения инфраструктуры зеркал crates.io.

>>> Подробный список изменений

>>> Подробности

★★★★★

Проверено: Falcon-peregrinus ()
Последнее исправление: Falcon-peregrinus (всего исправлений: 10)
Ответ на: комментарий от loyd

вряд ли здесь какой-то rustоман будет утверждать, что там есть ООП

Не, ну можно попробовать, просто в целях флейма: ООП - это святая троица «полиморфизм, инкапсуляция и наследование»; полиморфизм в Rust есть, инкапсуляция тоже, и даже наследование есть - наследование трейтов (ведь святая троица ничего не говорит о том, какое именно наследование должно быть). Так что Rust - ООП-язык %)

tailgunner ★★★★★
()
Ответ на: комментарий от red75prim

Наследование реализаций для ООП не обязательно.

Тогда какой смысл в наследовании?

Формального определения, насколько я знаю, нет.

Если не договориться не терминах и определениях, тогда разговор теряет смысл.

eao197 ★★★★★
()
Ответ на: комментарий от asaw

Опять пришли к тому с чего начали, это средство называется модуль, и Rust гарантирует что ты не сможешь изменить состояние обьекта за пределом его модуля. Значит и состояние и поведение будут всегда находится в одном модуле как «единая сущность».
Вот я набросал пример улитки, можешь попробовать заставить ее полететь:
https://is.gd/nhpP3X
Если получится, пиши.

pftBest ★★★★
()
Ответ на: комментарий от loyd

Кто это тут говорит из людей, которые хотя бы документацию раста освоили, что в языке нет инкапсуляции?

А разве инкапсуляция не противоречит системе трейтов? Вот выше red75prim показал пример со структурой S. Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что? Вынесение ацессора для этого поля в некий public_interface будет означать отказ от инкапсуляции.

С точки зрения какого-нибудь smalltalk-а там и не ООП вообще, в отличие от современного догмата «ООП = классовая модель + троица».

Боюсь, вы попутали SmallTalk с Self-ом и JavaScript-ом, емнип, в SmallTalk все нормально с классами, и со святой троицей. Кроме того, лично я вообще ничего про классы не говорил, ограничивая ООП только наследованием, инкапсуляцией и полиморфизмом.

eao197 ★★★★★
()
Ответ на: комментарий от pftBest

Я все пытаюсь вытянуть из тебя что же все таки мешает расту иметь инкапсуляцию, но все твои аргументы сводятся к

Этого в расте нет, точка.

А я, кажется, понял, о чем он - методы структуры не описываются в теле struct.

tailgunner ★★★★★
()
Ответ на: комментарий от eao197

Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

То пишем реализацию внутри модуля M, или отправляем PR на реализацию трейта, если модуль чужой.

red75prim ★★★
()
Ответ на: комментарий от asaw

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

Нет, не является. Твоё школьное определение инкапсуляции обладает нулевой практической ценностью, а наследование реализации успешно (пусть и не всегда с лёгкостью) заменяется композицией.

Esper
()
Ответ на: комментарий от asaw

У нас тут поведением и состоянием что обладает?

Модуль.

Какая сущность какого типа?

А где написано что у сущности должен быть тип? Ты пытаешься натянуть на определение инкапсуляции свойства класса, хотя совсем не обязательно иметь класс чтобы получить инкапсуляцию.
P.S. Жду свою летающую улитку.

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от eao197

Если для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

То ничего. Если это не твой модуль, то либо владелец оправданно это поле спрятал, либо у кого-то из вас проблема с проектированием.

Покажи как ты будешь организовывать доступ к приватному полю в c++, не применяя грязные хаки?

WatchCat ★★★★★
()
Ответ на: комментарий от pftBest

Модуль

То есть по-твоему модуль в расте обладает поведением и состоянием... До чего только не договоришься, чтобы не признать очевидное.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Плохая терминология, но, например свободная функция внутри модуля имеет право на модификацию состояния, как это описать в терминах владения и обладания я не знаю, но это не является нарушением инкапсуляции.

pftBest ★★★★
()
Ответ на: комментарий от tailgunner

Попробовать-то можно, но собеседник всё равно будет считать, что ООП это «как у меня в С++/Java/C#». Такие и JS за ООП язык не считают, чего уж там расту.

loyd
()
Ответ на: комментарий от WatchCat

Если это не твой модуль, то либо владелец оправданно это поле спрятал, либо у кого-то из вас проблема с проектированием.

Отлично.

В кривых ООЯ, вроде C++ и Java, есть возможность сделать protected члены (как атрибуты, так и методы), что дает возможность использовать наследование реализации и, при этом, переиспользовать части базового класса.

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

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

Ну и зачем после этого пытаться говорить, что в Rust-е поддерживается ООП?

eao197 ★★★★★
()
Ответ на: комментарий от AUX

Ну так многие и на C пишут, и дискомфорта не чувствуют.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

сли для реализации некоторого трейта T нужен доступ к приватному полю S, то что?

Не надо ничего выносить, нужно реализовать трейт для нашего S:

impl T for S {
  fn foo(&self) -> {
    // Есть доступ к приватным, но только если мы владельцы S
  }
}

Боюсь, вы попутали SmallTalk с Self-ом и JavaScript-ом, емнип, в SmallTalk все нормально с классами, и со святой троицей.

Self и JS тоже неплохой аргумент, но я говорил именно о SmallTalk-е и его создателе (авторе ООП, Алан Кей), который не считает С++ ООП-языком, например.

loyd
()
Ответ на: комментарий от eao197

Тут либо открываем структуру для того, чтобы можно было делать реализации чужих трайтов для нее (и тогда прощай инкапсуляция)

Ты так говоришь, как будто protected это не «прощай инкапсуляция»

loyd
()
Ответ на: комментарий от loyd

// Есть доступ к приватным, но только если мы владельцы S

Начинаю подозревать, почему доводы asaw-а не понимают.

Self и JS тоже неплохой аргумент, но я говорил именно о SmallTalk-е и его создателе (авторе ООП, Алан Кей), который не считает С++ ООП-языком, например.

Чтобы называть Алана Кея автором ООП, нужно, блин, совсем ничего не знать про Симула.

Еще и Джо Армстронг считает Erlang образцом ООП, давайте еще и это сюда приплетем.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Птица-секретарь

Выдержка ниже. Взято из Википедии.

Симула традиционно считается первым в мире объектно-ориентированным языком, но создатель языка Smalltalk Алан Кэй утверждает, что изобрёл термин «ООП»
Simula 67 явилась первым языком с встроенной поддержкой основных механизмов объектно-ориентированного программирования. Этот язык в значительной степени опередил своё время, современники (программисты 60-х годов) оказались не готовы воспринять ценности языка Simula 67

anonymous
()
Ответ на: комментарий от loyd

Ну тогда расскажите, как же protected нарушает инкапсуляцию? Ведь если есть класс B, унаследованный от класса A, то экземпляр класса B отнюдь не перестает быть экземпляром класса A (не будем сейчас брать в расчет варианты с private-наследованием). Соответственно, атрибуты класса A остаются доступны только внутри класса A.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Иногда заявляется даже большее: наследование [реализаций] нарушает инкапсуляцию, хотя по факту имеют в виду именно защищённые поля:

— «Because inheritance exposes a subclass to details of its parent's implementation, it's often said that inheritance breaks encapsulation» (у Банды Четырёх)

— «Unlike method invocation, inheritance violates encapsulation [Snyder86]. In other words, a subclass depends on the implementation details of its superclass for its proper function.» у Д. Блоха.

— «But then closely related concepts should not be separated into different files [..] Indeed, this is one of the reasons that protected variables should be avoided.» у Б. Мартина в Clean Code.

— Ещё стоит поискать «inheritance breaks encapsulation» у Макконнелла.

loyd
()
Ответ на: комментарий от eao197

Ну тогда расскажите, как же protected нарушает инкапсуляцию?

Если инкапсуляция по терминологии asaw то, реализация отдельно от объявления полей - нарушение инкапсуляции.

anonymous
()
Ответ на: комментарий от loyd

базовый класс недостаточно инкапсулирован от производных

Если под инкапсуляцией понимается объединение данных и кода их обрабатывающего, то данная фраза слишком заумна для моего понимания.

Пожалуй сольюсь и оставлю экспертов выдумывать про ООП новые небылицы.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ведь если есть класс B, унаследованный от класса A, то экземпляр класса B отнюдь не перестает быть экземпляром класса A [...]

Только в том случае, если выполняется принцип подстановки Лисков, а для этого недостаточно написать class B: A.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Извините, но я слился. У меня не хватает мозгов, чтобы понять слова собеседников. Так, я не понимаю, каким боком принцип подстановки Лисков относится к нарушению инкапсуляции, например, в этом случае:

class MyIntVector {
  protected :
    int * begin_;
    int * end_;
    size_t capacity_;
  public :
    ... // Ни одного виртуального метода.
};
...
class MyIntVectorWithDebugPrint : public MyIntVector {
  public :
    void debug_print() const {
      std::cout << this << ": capacity=" << capacity_
        << ", begin=" << begin_ << ", end=" << end_
        << ", size=" << (end_ - begin_) << std::endl;
    }
};

Прошу прощения, но дальше без меня.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Логику же Rust-оманов в данном случае вообще понять не могу. Ну вот есть устоявшееся определение ООП. Ну не подходит Rust под это определение

Внезапно: средства поддержки ООП бывают разные. Про смоллтолк и джаваскрипт уже написали. Кстати, привет asaw'у с его неприятием добавления трейта к существующему классу, интересно, что он думает о js с возможностью менять поведение прямо в рантайме у конкретного экземпляра:D. Добавлю, что даже внутри «семьи» c++ наследование и инкапсуляция реализованы существенно по-разному. C++ позволяет множественное наследование интерфейсов, данных и поведения, притом есть 2 варианта наследования, простое и виртуальное. C# позволяет одиночное наследование данных и поведения, и множественное наследование интерфейсов. Java позволяет одиночное наследование данных и множественное наследование интерфейсов и поведения. Раст в этом плане недалеко ушёл, всего лишь запретили наследование данных. В плане инкапсуляции тоже по-разному, в c++ нагородили protected и friend, косящие под c++, Java и c# унаследовали всё, кроме френдов, вместо них добавив internal и «default», rust же решили ограничится закрытием реализации внутри модуля. Для впитавшего c++ с молоком матери это кажется святотатством, но если забыть на минуту заветы старика Страуструпа и попытаться подумать о практическом смысле, станет ясно, что все эти методы ограничения нужны только для того, чтоб указать, мол вот эту часть можете использовать, а вот эта часть - не ваше дело, могу менять как хочу и не гарантирую надёжную работу при неправильном обращении. В этом плане закрытие реализации внутри модуля как раз является естественным, одновременно есть доступ у кого надо и толко у кого надо. Можно смело менять потроха, не боясь поломать внешние зависимости (естественно, при условии неизменности интерфейса). Собственно вся эта пурга с private/protected/friend была добавлена в C++ не от хорошей жизни, а от отсутствия модулей и даже неймспейсов в момент зарождения языка. Раст не пытается играть в совместимость с C++ и умеет модули от рождения, логично что способ разграничения интерфейса и потрохов заменили на более пристойный.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Внезапно: средства поддержки ООП бывают разные.

Внезапно: речь не про то, хорош ли подход Rust-а или плох, а про то, насколько правильно говорить о том, что Rust поддерживает ООП.

А то ведь можно договориться до того, что в C вручную можно сделать ООП и поэтому некий абстрактный хрюндель будет считать C объектно-ориентированным языком.

Добавлю, что даже внутри «семьи» c++ наследование и инкапсуляция реализованы существенно по-разному.

Вы еще добавьте, что это семья Симулы, а не C++. И что в мире статически-типизированных языков есть еще и Eiffel с нормальным множественным наследованием. А то ваш поток сознания не выглядит достаточно авторитетно.

ЗЫ. Менять поведение конкретного экземпляра в рант-тайме можно не только в JS, но и в SmallTalk/Ruby и, емнип, в Python-е. Это имеет большее отношение к динамической типизации, нежели к ООП.

eao197 ★★★★★
()
Ответ на: комментарий от AUX

Тем не менее троица из инкапсуляции, наследования и полиморфизма есть во всех успешных ООЯ. Так что мне не о чем беспокоится.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

В Golang нет наследования, но он успешен.

AUX ★★★
()
Ответ на: комментарий от eao197

Если под инкапсуляцией понимается объединение данных и кода их обрабатывающего, то данная фраза слишком заумна для моего понимания.

Боюсь, это только в терминологии asaw понимается так буквально. Причём это не шибко-то работает даже для C++, так как данные-члены объявляются в .h файле, а работа с ними - в .cpp. В реальности инкапсуляция названа так из аналогии с другими областями, там под этим понимают изоляцию чего-то внутри другого чего-то, отсюда и общий корень с капсулой. Капсула - публичный интерфейс. Внутри - закрытая реализация. Наследование, как таковое, не всегда нарушает инкапсуляцию, но дело в том, что в практических случаях это происходит довольно часто. Поэтому и предлагают заменять наследование композицией, при композиции нет неявных зависимостей, делегация «предку» производится явно и только через его публичный интерфейс. Чисто для примера.

class List<T>:IList<T>
{
   public void insert(int index, T item){...}
   public void add(T item){...}
...
}
Пусть мне нужно реализовать отладочный список, который печатает добавляемый элемент или там наблюдаемый, который сообщает о добавлениии элементов. Что переопределяем? Если в List<T> add(T item) реализован через insert(Count, item), то нужно переопределять только insert, иначе нужно переопределить и insert и add. Вот и нарушение инкапсуляции, зависимость от подробностей реализации, причём даже без каких-либо попыток доступа к защищённым членам.

khrundel ★★★★
()
Ответ на: комментарий от eao197

про то, насколько правильно говорить о том, что Rust поддерживает ООП.

Чего тут обсуждать? Поддерживает.

А то ведь можно договориться до того, что в C вручную можно сделать ООП и поэтому некий абстрактный хрюндель будет считать C объектно-ориентированным языком.

Ну вот вы сами и ответили. В C - вручную. А в расте не вручную. Вопрос можно закрывать.

Вы еще добавьте, что это семья Симулы, а не C++. И что в мире статически-типизированных языков есть еще и Eiffel с нормальным множественным наследованием. А то ваш поток сознания не выглядит достаточно авторитетно.

Пиписькомерство началось что ли? Ну да, я Симулу не знаю и знать не хочу. Есть что по факту возразить? Вот в C# я не могу наследоваться от 2х типов сразу, только через интерфейс и потому вынужден реализовывать методы второго предка сам заново, давайте рассказывайте мне про то, что в C# - это примерно как C с ручной реализацией ООП.

khrundel ★★★★
()
Ответ на: комментарий от void_ptr

tailgunner

Нельзя сделать список элементов разных типов, но поддерживающих (реализующих) один интерфейс (трейт)

Можно.
По дефолту в расте предпочтителен статический полиморфизм, но для тем кому хочется странного динамического — есть Trait Objects.

fn main() {
    let number = 1;
    let string = "string".to_string();
    let vector = vec![1, 2, 3];

    let debug_vec : Vec<&Debug> = vec![&number, &string, &vector];   

    println!("{:?}", debug_vec);
}
stdout: [1, "string", [1, 2, 3]]

mersinvald ★★★★★
()

Если смотреть на сабж, он как был уныл и неюзабилен, так и остался.

AUX ★★★
()
Ответ на: комментарий от tailgunner

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

mersinvald ★★★★★
()
Ответ на: комментарий от mersinvald

есть Trait Objects

Значит есть способ обойти обязательную мономорфизацию? Это, блин, дико правильно.

nezamudich ★★
()
Ответ на: комментарий от khrundel

Если так, то это возможно только для ограниченного числа ситуаций.

tailgunner ★★★★★
()
Ответ на: комментарий от khrundel

А можно пример такого использования енумов? Если это можно сделать без ансейфа, это мегакруто, хоть и абсолютно бесполезно :)

mersinvald ★★★★★
()
Ответ на: комментарий от loyd

С точки зрения какого-нибудь smalltalk-а там и не ООП вообще, в отличие от современного догмата «ООП = классовая модель + троица».

«When I invented OOP, i wasn't mean C++» (c) Alan Key

В классическом смоллтоке (что становится ясно если почитать статьи Алана Кея, например ранняя история смоллтока) была реализована модель акторов Карла Хьюита + пару инсайтов Кея + реализация от Дэна Ингалса (который не знал CL и CLOS, например, а знал бейсик. и поэтому изобрёл что-то отличное от CLOS)

эта идея с 70х прослеживается в : SmallTalk, Self, JavaScript, Pony, Scheme.

Scheme вообще появился в конце тех же 70х как скрещивание лямдба-исчисления с моделью акторов (ну и уже позже 'let over lamda' Дага Хойта, как осмысление подхода)

да, там не «ООП = классовая модель + троица».

а, скорее «ООП = метаобъектная модель + динамическая диспетчеризация»

динамическая диспетчеризация позволяет реализовать прокси-объекты, протоколы и категории в Objective C (то есть, более reusable интерфейсы)

в объектной модели COS эта тема ещё более развёрнута: «мультидиспетчеризация и мультипараметризация»

вообще, надо различать ООП как идею, концепцию, метапарадигму :-)

и конкретную реализацию объектной модели (например, ООП как в Simula67/C++85, Beta : «ООП = классовая модель + троица» )

anonymous
()
Ответ на: комментарий от anonymous

например, в CLOS/CL другая объектная модель. в смоллток — третья. в Eiffel — четвёртая (похожая на классическую троицу, но более reusable: reversibility/seamlessness, BON method, Eiffel method, Design By Contract, компиляторно-вычисляемый virtual, частично абстрактный/абстрагированный в потомках метод/свойство и наоборот(из абстрактного в конкретное), замена в потомках свойства на метод и наоборот, ковариантность/контравариантность, retry в исключениях, catcalls, SCOOP.)

anonymous
()
Ответ на: комментарий от khrundel

Боюсь, это только в терминологии asaw понимается так буквально. Причём это не шибко-то работает даже для C++, так как данные-члены объявляются в .h файле, а работа с ними - в .cpp.

OMG. Да ты же не понимаешь о чем говоришь. Ты пишешь об отделении деталей реализации от определения типа и говоришь, что это плохо для инкапсуляции. Назови это абстрагированием. В любом случае как раз это-то работает на пользу инкапсуляции/абстрагированию. Ладно, я уже понял, что любители раста - так же любители поиграть в слова.

asaw ★★★★★
()
Ответ на: комментарий от eao197

Ни одного виртуального метода

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

red75prim ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.