Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Read-only Typed Arrays #37792

Closed
5 tasks done
ImBoop opened this issue Apr 4, 2020 · 15 comments
Closed
5 tasks done

Support for Read-only Typed Arrays #37792

ImBoop opened this issue Apr 4, 2020 · 15 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@ImBoop
Copy link

ImBoop commented Apr 4, 2020

Search Terms

ReadonlyTypedArray Int8Array Uint8Array Uint16Array Int16Array Uint32Array Int32Array Float32Array Float64Array

Suggestion

It would be nice if we had readonly or otherwise immutable typed arrays.

Use Cases

I would like to return my array directly for performant bulk access to the data within, but make sure only the methods on the object itself are used to alter it.

Examples

Ideally it'd be just like ReadonlyArray, in that you'd return ReadonlyTypedArray

I tried the following workaround:

export type ReadonlyTypedArray<T> = T | {
	readonly [n: number]: number;
} ;

This, as expected, only has the indexing available, none of the methods/fields available in T are accessible (and if you use & instead of |, it doesn't override the indexing operator)

My current workaround is to do this:

export interface ReadonlyUint16Array extends Uint16Array {
	readonly [n: number]: number;
};

Like ReadonlyArray, this would only be a static readonly enforcement, not runtime.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript labels Jun 1, 2020
@RyanCavanaugh
Copy link
Member

You can do this today with existing type system features; I don't think this necessitates defining something built-in.

@ImBoop
Copy link
Author

ImBoop commented Jun 4, 2020

You can do this today with existing type system features; I don't think this necessitates defining something built-in.

Do you mind explaining how? Nothing I did worked to be able to return read-only typedarrays

@ImBoop
Copy link
Author

ImBoop commented Jul 17, 2020

No answer or clarification prior to closing?

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jul 20, 2020

@ImBoop If you're not sure how to accomplish some task, that's a question, and this isn't a support forum. If you want to show more explicitly what you were trying to do and what wasn't working with it, we can often advise.

@RReverser
Copy link
Contributor

@RyanCavanaugh But you can't do this with existing types... At least, not without manually and carefully defining all the methods that can operate on readonly typed array.

TypeScript already defines ReadonlyArray as a built-in rather than leaving it to userland, so why not ReadonlyTypedArray for symmetry?

@ImBoop
Copy link
Author

ImBoop commented Jan 30, 2021

But you can't do this with existing types... At least, not without manually and carefully defining all the methods that can operate on readonly typed array.

This was the bulk of the issue -- it required recreating the entire class and even then you'd run into issues where your type couldn't be used without a headache inducing process as the readonly typed array would have to omit methods that wrote to said array.

I ended up not following up with their comment because I felt that the closing of it without comment was very unprofessional -- mainly because I didn't see his edit as I saw the comment via email (nevermind that they claimed it was possible without backing it up...)

As a usecase and clarified proposal I guess:

Typed arrays are often far better in terms of performance compared to standard arrays (which can be sparse which is why I presume they can be slower)

The benefit of a readonly typed array is for, at least in my use case when this was created, making sure the caller couldn't modify a block of data without using a method e.g., a getData() method would return a readonly Uint16Array and if they wanted to make changes, they'd have to call a similar setData(index, newValue) that could process the necessary logic that would handle the data update.

Like ReadonlyArray, this would remove methods that could alter the array from the definition (which, notably, is where a lot of issues you'd run into would come into play if you wanted to use the two types together) such as fill().

The idealistic proposal would also support using the readonly keyword on a typedarray, in a similar manner as it currently works for standard JS arrays.

@yvele
Copy link

yvele commented May 30, 2022

Here is a workaround based on https://www.growingwiththeweb.com/2020/10/typescript-readonly-typed-arrays.html

/**
 * Typeds array mutable properties.
 */
type MutableProperties = "copyWithin" | "fill" | "reverse" | "set" | "sort";

/**
 * Describes a `Uint8ClampedArray` that can only be read from.
 * Any variable with a reference to a `ReadonlyUint8ClampedArray` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyUint8ClampedArray extends Omit<Uint8ClampedArray, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Uint8Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyUint8Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyUint8Array extends Omit<Uint8Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Uint16Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyUint16Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyUint16Array extends Omit<Uint16Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Uint32Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyUint32Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyUint32Array extends Omit<Uint32Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Int8Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyInt8Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyInt8Array extends Omit<Int8Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Int16Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyInt16Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyInt16Array extends Omit<Int16Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Int32Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyInt32Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyInt32Array extends Omit<Int32Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Float32Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyFloat32Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyFloat32Array extends Omit<Float32Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `Float64Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyFloat64Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyFloat64Array extends Omit<Float64Array, MutableProperties> {
  readonly [n: number]: number;
}

/**
 * Describes a `BigInt64Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyBigInt64Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyBigInt64Array extends Omit<BigInt64Array, MutableProperties> {
  readonly [n: number]: bigint;
}

/**
 * Describes a `BigUint64Array` that can only be read from.
 * Any variable with a reference to a `ReadonlyBigUint64Array` can’t add, remove,
 * or replace any elements of the array.
 */
export interface ReadonlyBigUint64Array extends Omit<BigUint64Array, MutableProperties> {
  readonly [n: number]: bigint;
}

@ImBoop
Copy link
Author

ImBoop commented May 30, 2022

@yvele

Fantastic workaround and good find. Disappointed that the TS team continues to stick their head in the mud with this issue, but this will be very useful for those that find this issue in the future, with the notable caveat that if TypedArrays are extended in the future with other methods that write to it, it will stop (fully as intended, anyway) working (hence the purpose of this issue to begin with, for first class support of the types a la readonly arrays have.)

@aidenfoxx
Copy link

@yvele Thanks for posting the workaround! Would love to see this implemented natively.

@cshaa
Copy link

cshaa commented Jun 27, 2022

@RyanCavanaugh @RReverser Any plans to reopen this issue, as it's clearly not resolved?

@kaya3
Copy link

kaya3 commented Aug 14, 2022

The workaround can be made less brittle by declaring

type MutableProperties = Exclude<keyof unknown[], keyof readonly unknown[]>

It's very likely that any new mutation methods added to typed arrays will also be added to regular arrays, so this should be future-proof. Note that this also gets some keys like push and pop which don't exist on typed arrays, but that doesn't cause any problems with Omit.

@ASDFGerte
Copy link

I wanted a proper readonly TypedArray, came to see if there is a built-in version similar to arrays, and got the answer that there isn't. Imho, the issue is indeed resolved, and the answers were on point. I'll will write one myself, or use a library, and don't see a big problem with it.

While it would have been nice, if TS had a built-in readonly TypedArray, as has been detailed, there is no necessity for it. Outsourcing the maintenance of such utility types means the resources otherwise used for it can be allocated for more important things. Overall, I agree with that decision. There are far more important issues open, and this can be solved without additions to TS itself.

@Cinderlex
Copy link

@yvele @kaya3 Thanks for the great workarounds! A couple of thoughts:

  • results of subarray and valueOf can be used to mutate original typed array, so we should probably both exclude them from properties and add them to the interfaces with readonly return types
type MutableProperties = "copyWithin" | "fill" | "reverse" | "set" | "sort" | "subarray" | "valueOf";

export interface ReadonlyUint8Array extends Omit<Uint8Array, MutableProperties> {
    readonly [n: number]: number;
    subarray(begin?: number, end?: number): ReadonlyUint8Array;
    valueOf(): ReadonlyUint8Array;
}
  • Not sure why I have at in the Exclude<keyof unknown[], keyof readonly unknown[]> union - it cannot be used in left-hand position. Maybe something off with my config.

@Cinderlex
Copy link

And it's still mutable through the 3-rd argument of every/filter/find etc. It seems like a better solution would be recreating original typed array api with readonly tweaks and removal of all mutating methods. Like this https://gist.github.com/Cinderlex/167a8e18afb8da15f03358ced695a3af . It also seems to become mutable counterpart's supertype (cause structural typing).

@SamB
Copy link

SamB commented Mar 10, 2023

Remember, just because it doesn't require any new language features doesn't mean it that it shouldn't be in the standard library ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

10 participants