Каков современный канонический способ поддержки преобразования ContiguousBytes как в массив, так и в «не-массив»?

Например, без этой второй перегрузки загрузка массива даст «UnsafeRawBufferPointer.load выходит за пределы». Есть ли способ справиться с обоими случаями без перегрузок?

let bytes: [UInt8] = [1, 0, 1, 0]
bytes.load() as Int32 // 0x1_00_01
bytes.load() as [Int16] // [1, 1]
public extension ContiguousBytes {
  func load<T>(_: T.Type = T.self) -> T {
    withUnsafeBytes { $0.load(as: T.self) }
  }

  func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
    withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
  }
}

АФАИК, ты не можешь.

Leo Dabus 24.03.2022 18:58

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

Leo Dabus 24.03.2022 19:08

Неважно, я забыл, что вам нравится создавать общие типы, используя одно и то же имя вместо одного символа. Кстати, почему бы не просто func load<Element>() -> [Element] {?

Leo Dabus 24.03.2022 19:14

Аргументы позволяют вам передать метатип, как они это делают, например. Кодируемый. Я не склонен использовать метатипы таким образом, но это не будет API, если мы когда-либо не получим общие свойства.

Jessy 24.03.2022 19:24

Да я знаю, что это позволяет передать тип. Я просто не вижу никакого преимущества перед явной установкой результирующего типа или приведением при вызове этого метода, как вы.

Leo Dabus 24.03.2022 19:27

Я тоже нет, но явно бытует другое мнение — иначе у нас не было бы ужасного требования использовать метатипы с Codable. Поэтому я всегда пишу эти функции вот так. Возможно, я делаю глупый выбор. ?

Jessy 24.03.2022 20:11

Итай Фербер — тот, кто спрашивает об этом

Leo Dabus 24.03.2022 20:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
40
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

С точки зрения наличия двух перегрузок: вы, к сожалению, не можете их избежать. ContiguousBytes.withUnsafeBytes дает вам доступ к базовому хранилище типа, который реализует ContiguousBytes, поэтому буфер, заданный, скажем, [UInt8].withUnsafeBytes, будет фактическим буфером, который экземпляр массива использует для данных хранить, а не указателем на сам в памяти (например, его длина, емкость, указатель памяти и т. д.).

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

withUnsafeBytes { rawBuffer in
    rawBuffer.load(as: [Int16].self)
}

где load(as:) пытается прочитать [1, 0, 1, 0], как если бы это содержимое было длиной, емкостью и указателем буфера одного экземпляра [Int16], но это не так — это всего лишь содержаниеbytes.

Вам нужна вторая перегрузка, чтобы создать новый массив с содержание этого UnsafeRawBufferPointer.


Что касается фактической реализации вашего варианта массива, начиная с SE-0333 Расширение возможностей использования withMemoryRebound, появившегося в Swift 5.7, правильный способ сделать это - использовать withMemoryRebound(to:_:), поскольку безусловное bindMemory(to:) небезопасно, если буфер уже привязан к другому типу. withMemoryRebound(to:_:) теперь всегда безопасно, даже если буфер привязан, если вы соответствуете требованиям выравнивания:

/// A raw buffer may represent memory that has been bound to a type.
/// If that is the case, then T must be layout compatible with the
/// type to which the memory has been bound. This requirement does not
/// apply if the raw buffer represents memory that has not been bound
/// to any type.

Это будет выглядеть как

func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
    withUnsafeBytes { rawBuffer in
        rawBuffer.withMemoryRebound(to: Element.self) { typedBuffer in
            Array(typedBuffer)
        }
    }
}

UnsafeRawBufferPointer.withMemoryRebound(to:_:) выполнит математику привязки необработанных байтов к правому шагу Element, хотя имейте в виду, что это допустимо для Только, если выравнивание необработанного буфера соответствует требованиям выравнивания Element — в противном случае вы либо выйдете из строя, либо сработаете. неопределенное поведение.

Так это доступно в Xcode 13.3?

Leo Dabus 24.03.2022 19:51

@LeoDabus Должно быть, да.

Itai Ferber 24.03.2022 19:51

Однако это не так. ? Я буду следить за этим и отмечу это как ответ, если он в конечном итоге сработает. Между тем, ваш ответ неточен без квадратных скобок из моей подписи — эта перегрузка применима только к метатипам массива. Я также предполагаю, что вы можете использовать Array.init напрямую вместо создания нового замыкания…?

Jessy 24.03.2022 20:10

Действительно, я ошибался — я считаю, что предложение изначально предназначалось для Swift 5.6, но появится в Xcode только в Swift 5.7. Я попробовал онлайн-площадку Swift со Swift 5.6, и у нее действительно было withMemoryRebound (так что я не совсем это выдумывал! ?), но похоже, что она не попала в сам Xcode 13.3.

Itai Ferber 24.03.2022 20:13

@ItaiFerber да, Xcode 13.3 — это Swift 5.6, поэтому нам нужно будет дождаться следующего выпуска.

Leo Dabus 24.03.2022 20:16

@Jessy Извините за эту опечатку, обновил ответ, чтобы явно применить метатип Array.

Itai Ferber 24.03.2022 20:18

Попробовал ваш код в Swift 5.7, используя набор инструментов для разработки стволов. ??

Jessy 27.03.2022 03:00

Другие вопросы по теме