void main() {
foo(() {});
foo((_) {});
}
void foo(Function callback) => null;
How can I limit callback to only have either VoidCallback or ValueChanged. Currently, it accepts all types of Function.
You cannot limit callback to VoidCallback or to ValueChanged at compilation time. You could use runtime checks:
void foo(Function callback) {
if (callback is! VoidCallback && callback is! ValueChanged) {
throw ArgumentError(
'Only VoidCallback and ValueChanged callbacks are allowed');
}
// ...
}
However, I don't recommend that since it makes your API harder to use. As an example, see Future.catchError which similarly tries to accept multiple callback types and checks them at runtime. Unfortunately, it:
Reduces self-documentability of the code, and documentation can be awkward. (For the case of Future, the error callback documentation is confusingly in Future.then.)
Trades static type checks for runtime checks, which makes mistakes harder to catch.
In your case, IMO it would be simpler to always take a ValueChanged callback, and callers can trivially ignore the argument if it's not needed:
void main() {
foo((_) {});
foo((_) => someVoidCallback());
}
void foo<T>(ValueChanged<T> callback) => null;
You cannot. What would you even do with such a datatype?
In other languages, you would use method overloading and have two methods, one for each type. Dart does not support that, see Why does dart not allow method overloading?
You can have two differently named methods though.
If you have some value with your callback that a ValueChanged method can make use of, then use that datatype. A consumer having only a VoidCallback will have to ignore the parameter, just as you did in your code.
Related
I have a function to override and I want it to do nothing. The base class function is abstract. I have two options for implementation:
// option 1
#override
Future<void> cancelReadStream() async {}
// option 2
#override
Future<void> cancelReadStream() {
return Future.value();
}
Dart code metrics tells me to avoid redundant async, so I thought it is best to return a Future<void>. Is Future.value() the best way to return a Future<void>?
I'd argue that the code metrics are wrong.
It's exactly the third item they list:
A value is returned and it will be impliclty wrapped in a future. async is shorter than Future.value(...).
In this case the value is null, and it's returned implicitly, but it's still shorter than Future<void>.value(null);
The code
#override
Future<void> cancelReadStream() async {}
is perfectly reasonable and readable. It's an asynchronous function (returns a future), which does nothing before completing that future.
I'd consider reporting it to the DCM team, as a false positive.
I would go with option 1, it's just the most solid implementation.
Dart metrics can be a little invasive sometimes, I would just ignore it.
Once you actually write any async code the warning will disappear anyway.
I'm currently experimenting with Isolates in dart.
I'm trying to create a wrapper around an Isolate to make it more pleasant to use.
The desired interface is something along the lines:
abstract class BgIsolateInterface {
Future<Response> send<Message, Response>(Message message);
}
I want to have a method that sends a message to the background interface and then return the response to the caller.
To achieve this I figured I have to create a new RawReceivePort or ReceivePort in the send function to reliably get the correct response.
But this would mean I'm essentially creating the port and discarding it. Going against the documentations which states
Opens a long-lived port for receiving messages.
So my questions are:
what exactly are ReceivePorts and RawReceivePorts?
would my use case be valid i.e. have them be created only to read a single response?
should I look at another way of doing things?
Note: Please don't suggest the Flutter compute function as an alternative. I'm looking to do this in a long running isolate so I can share services / state between function calls. I'm just not showing this here to keep the question short.
Thank you very much!!!
Edit #1:
When providing the answer I realised there was also an underling question about how to read the Dart source, more specifically how to find external methods' implementations. That question was added to the title. The original question was just: What exactly is a ReceivePort / RawReceivePort?.
Yesterday, I've searched across the source and I think, I now have the answers. If I'm wrong, anyone more involved with the engine please correct me. This is mostly my speculation.
TLDR:
ReceivePort/RawReceivePorts are essentially int ids with a registered message handler. The SendPort knows to which id i.e. ReceivePort/RawReceivePort it should send the data to.
Yes. But for another use case there is better way.
Change the interface, so we react to states / responses coming from the isolate i.e.
abstract class BgIsolateInterface<Message, Response> {
void send(Message message);
void listen(void Function(Response) onData);
}
Long
#1
I've looked at the implementation and I'm including my findings here also to put a note for my future self on how to actually do this if I ever need to.
First, if we look at the implementation of ReceivePort (comments removed):
abstract class ReceivePort implements Stream<dynamic> {
external factory ReceivePort([String debugName = '']);
external factory ReceivePort.fromRawReceivePort(RawReceivePort rawPort);
StreamSubscription<dynamic> listen(void onData(var message)?,
{Function? onError, void onDone()?, bool? cancelOnError});
void close();
SendPort get sendPort;
}
We can see the external keyword. Now, this means implementation is defined somewhere else. Great! Where?
Let's open the SDK source and look. We are looking for a class definition of the same name i.e. ReceivePort with a #patch annotation. Also it seems the Dart team follows the convention of naming the implementation files for these external methods with the suffix _patch.dart.
We then find the three of these patch files. Two for the js runtime, one for development and one for production, and one file for the native? runtime. Since, I'm not using Dart for the web, the latter is the one I'm interested in.
In the file: sdk/lib/_internal/vm/lib/isolate_patch.dart we see:
#patch
class ReceivePort {
#patch
factory ReceivePort([String debugName = '']) =>
new _ReceivePortImpl(debugName);
#patch
factory ReceivePort.fromRawReceivePort(RawReceivePort rawPort) {
return new _ReceivePortImpl.fromRawReceivePort(rawPort);
}
}
Ok, so the implementation for ReceivePort is actually a library private _ReceivePortImpl class.
Note: As you can see factory methods don't have to return the same class the method is defined in. You just have to return an object that implements or extends it. i.e., has the same contract.
class _ReceivePortImpl extends Stream implements ReceivePort {
_ReceivePortImpl([String debugName = ''])
: this.fromRawReceivePort(new RawReceivePort(null, debugName));
_ReceivePortImpl.fromRawReceivePort(this._rawPort)
: _controller = new StreamController(sync: true) {
_controller.onCancel = close;
_rawPort.handler = _controller.add;
}
SendPort get sendPort {
return _rawPort.sendPort;
}
StreamSubscription listen(void onData(var message)?,
{Function? onError, void onDone()?, bool? cancelOnError}) {
return _controller.stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
}
close() {
_rawPort.close();
_controller.close();
}
final RawReceivePort _rawPort;
final StreamController _controller;
}
Which as we can see is really just a wrapper around a RawReceivePort where the handler is a StreamController.add method. So, what about the RawReceivePort?
If we look at initial file where ReceivePort is defined we again see. It's just one external factory method and an interface for others.
abstract class RawReceivePort {
external factory RawReceivePort([Function? handler, String debugName = '']);
void set handler(Function? newHandler);
SendPort get sendPort;
}
Luckily, its #patch version can also be found in the same place as the ReceivePorts.
#patch
class RawReceivePort {
#patch
factory RawReceivePort([Function? handler, String debugName = '']) {
_RawReceivePortImpl result = new _RawReceivePortImpl(debugName);
result.handler = handler;
return result;
}
}
Ok, again the actual implementation is _RawReceivePortImpl class.
#pragma("vm:entry-point")
class _RawReceivePortImpl implements RawReceivePort {
factory _RawReceivePortImpl(String debugName) {
final port = _RawReceivePortImpl._(debugName);
_portMap[port._get_id()] = <String, dynamic>{
'port': port,
};
return port;
}
#pragma("vm:external-name", "RawReceivePortImpl_factory")
external factory _RawReceivePortImpl._(String debugName);
close() {
_portMap.remove(this._closeInternal());
}
SendPort get sendPort {
return _get_sendport();
}
bool operator ==(var other) {
return (other is _RawReceivePortImpl) &&
(this._get_id() == other._get_id());
}
int get hashCode {
return sendPort.hashCode;
}
#pragma("vm:external-name", "RawReceivePortImpl_get_id")
external int _get_id();
#pragma("vm:external-name", "RawReceivePortImpl_get_sendport")
external SendPort _get_sendport();
#pragma("vm:entry-point", "call")
static _lookupHandler(int id) {
var result = _portMap[id]?['handler'];
return result;
}
#pragma("vm:entry-point", "call")
static _lookupOpenPorts() {
return _portMap.values.map((e) => e['port']).toList();
}
#pragma("vm:entry-point", "call")
static _handleMessage(int id, var message) {
final handler = _portMap[id]?['handler'];
if (handler == null) {
return null;
}
handler(message);
_runPendingImmediateCallback();
return handler;
}
#pragma("vm:external-name", "RawReceivePortImpl_closeInternal")
external int _closeInternal();
#pragma("vm:external-name", "RawReceivePortImpl_setActive")
external _setActive(bool active);
void set handler(Function? value) {
final int id = this._get_id();
if (!_portMap.containsKey(id)) {
_portMap[id] = <String, dynamic>{
'port': this,
};
}
_portMap[id]!['handler'] = value;
}
static final _portMap = <int, Map<String, dynamic>>{};
}
OK, now we're getting somewhere. A lot is going on.
First thing to note are the: #pragma("vm:entry-point"), #pragma("vm:entry-point", "call") and #pragma("vm:external-name", "...") annotations. Docs can be found here.
Oversimplified:
vm:entry-point tells the compiler this class / method will be used from native code.
vm:external-name tells the compiler to invoke a native function which is registered to the name provided by the annotation.
For instance to know the implementation of:
#pragma("vm:external-name", "RawReceivePortImpl_factory")
external factory _RawReceivePortImpl._(String debugName);
We have to look for DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory. And we find the entry in: runtime/lib/isolate.cc.
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory, 0, 2) {
ASSERT(TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
Dart_Port port_id = PortMap::CreatePort(isolate->message_handler());
return ReceivePort::New(port_id, debug_name, false /* not control port */);
}
We see the port_id is created by PortMap::CreatePort and is of type Dart_Port. Hmmm, and what is a the type definition for Dart_Port.
runtime/include/dart_api.h
typedef int64_t Dart_Port;
OK so the actual internal representation of a RawReceivePort is a signed int stored in 64 bits, and some additional information like the type, state, debug names etc.
Most of the work is then being done in PortMap::CreatePort and other of its methods. I won't go in depth, because quite honestly I don't understand everything.
But from the looks of it the PortMap uses the port_id to point to some additional information + objects. It generates it randomly and makes sure the id is not taken. It also does a lot of different things but let's move on.
When sending a message through SendPort.send, the method essentially calls the registered entry SendPortImpl_sendInternal_ which determines which port to send the information to.
Note: SendPort essentially just points to its ReceivePort and also stores the id of the Isolate where it was created. When posting a message this id is used to determine what kind of objects can be sent through.
The a message is created and passed to PortMap::PostMessage which in turn calls MessageHandler::PostMessage.
There the message is enqueued by a call to MessageQueue::Enqueue. Then a MessageHandlerTask is ran on the ThreadPool.
The MessageHandlerTask essentially just calls the MessageHandler::TaskCallback which eventually calls MessageHandler::HandleMessages.
There the MessageHandler::HandleMessage is called, but this function is implemented by a child class of MessageHandler.
Currently there are two:
IsolateMessageHandler and
NativeMessageHandler.
We are interested in the IsolateMessageHandler.
Looking there we see IsolateMessageHandler::HandleMessage eventually calls DartLibraryCalls::HandleMessage which calls object_store->handle_message_function(). full chain: Thread::Current()->isolate_group()->object_store()->handle_message_function()
The function handle_message_function is defined by the (dynamic?) macro LAZY_ISOLATE(Function, handle_message_function) in runtime/vm/object_store.h.
The property + stores created are used in: runtime/vm/object_store.cc by the: ObjectStore::LazyInitIsolateMembers.
_RawReceivePortImpl is registered to lazily load at the isolate_lib.LookupClassAllowPrivate(Symbols::_RawReceivePortImpl()) call.
As well as, the methods marked with #pragma("vm:entry-point", "call"), including static _handleMessage(int id, var message).
Which is the handler that ->handle_message_function() returns.
Later the DartLibraryCalls::HandleMessage invokes it through DartEntry::InvokeFunction with the parameters port_id and the message.
This calls the _handleMessage function which calls the registered _RawReceivePort.handler.
#2
If we compare the Flutter's compute method implementation. It spins up an Isolate and 3 ReceivePorts for every compute call. If I used compute, I would be spending more resources and loose context between multiple message calls I can have with a long-running Isolate. So for my use case I reason, creating a new ReceivePort everytime I pass a message shouldn't be a problem.
#3
I could use a different approache. But I still wish to have a long running Isolate so I have the flexibility to share context between different calls to the Isolate.
Alternative:
Would be following a bloc / stream style interface and have a method to assign a listener and a method to send or add a message event, and have the calling code listen to the responses received and act accordingly.
i.e. an interface like:
abstract class BgIsolateInterface<Message, Response> {
void send(Message message);
void addListener(void Function(Response) onData);
void removeListener(void Function(Response) onData);
}
the down side is the Message and Response have to be determined when creating the class rather than simply when using the send method like the interface in my question. Also now some other part of the code base has to handle the Response. I prefer to handle everything at the send call site.
Note: The source code of the Dart project is put here for presentation purposes. The live source may change with time. Its distribution and use are governed by their LICENSE.
Also: I'm not C/C++ developer so any interpretation of the C/C++ code may be wrong.
While this answer is long side-steps the questions a little bit, I find it useful to include the steps to search through the Dart source. Personally, I found it difficult initially to find where external functions are defined and what some of the annotation values mean. While these steps could be extracted into a separate question, I think it's useful to keep it here where there was a use case to actually dive deep.
Thank you for reading!
I wonder if there's a language sugar/SDK utility function in Dart that allows to protect a certain code from running more than once?
E.g.
void onUserLogin() {
...
runOnce(() {
handleInitialMessage();
});
...
}
I know I can add a global or class static boolean flag to check but it would be accessible in other functions of the same scope with a risk of accidental mixup in the future.
In C++ I could e.g. use a local static bool for this.
There is no built-in functionality to prevent code from running more than once. You need some kind of external state to know whether it actually did run.
You can't just remember whether the function itself has been seen before, because you use a function expression ("lambda") here, and every evaluation of that creates a new function object which is not even equal to other function objects created by the same expression.
So, you need something to represent the location of the call.
I guess you could hack up something using stack traces. I will not recommend that (very expensive for very little advantage).
So, I'd recommend something like:
class RunOnce {
bool _hasRun = false;
void call(void Function() function) {
if (_hasRun) return;
// Set after calling if you don't want a throw to count as a run.
_hasRun = true;
function();
}
}
...
static final _runOnce = RunOnce();
void onUserLogin() {
_runOnce(handleInitialMessage);
}
It's still just a static global that can be accidentally reused.
It's almost like so I can write an async that is intended not to be awaitable outside by designating it as, say, void not Future<void>
void fetchUserOrder() async {
// Imagine that this function is fetching user info from another service or database.
await Future.delayed(Duration(seconds: 2));
print('Large Latte');
print('After Large Latte');
}
void main() async {
fetchUserOrder();
await Future.delayed(Duration(seconds: 1));
print('Fetching user order...');
print('After Fetching user order...');
}
Print:
Fetching user order...
After Fetching user order...
Large Latte
After Large Latte
This is sooo so unallowed in .NET and also c++20 realm.
I wonder why dart let async functions return non-Future?
Wouldn't this end up with intended hidden async in API all over the places?
Dart doesn't force you to await futures. Futures existed before await was introduced, so it couldn't really.
That means that you can always not await a future. There are lints to make it harder to avoid, but nothing prevents you from just defining ignore(Object? _){} and doing ignore(futureOperation());.
Since a Future is an object, you can assign Future to Object and to anything you can assign Object to, including void. That also means that an async function can be cast to a function type with a return type of void, like:
void Function() f = () async { .... };
You can call that and ignore the result.
And that too is why you can declare the return type of an async method to be void or dynamic or Object?. Because the type system has to allow it anyway.
It would have been different if Future was not a subtype of Object, but that would also make a lot of things more complicated.
It can be used deliberately. If you actually do want a function which does asynchronous operations in the background, but you don't want anyone to wait for them, you can do so:
void log(String message) async {
(await getLogger()).log(message);
}
This function will (eventually) log the message, but you don't have to wait for it to do so.
You could write essentially the same function without using async too, so forcing disallowing a void return type with async would just be an unnecessary obstacle, forcing you to rewrite the same thing in a less readable way.
void log(String message) {
getLogger().then((logger) {
logger.log(message);
});
}
(It's not exactly the same because the async version currently does return a future, it just tells you not to use it but typing it as void. You can, even if you really shouldn't, cast the void return to dynamic and await it anyway. The non-async version doesn't return a future at all.)
Dart offers a FutureOr class, that allows writing:
FutureOr<int> future;
future = 42; // valid
future = Future.value(42); // also valid
I would assume that FutureOr would be useful to remove the unnecessary delay caused by the event loop if the value can be read synchronously.
But that doesn't seem to be the case, as showcased by:
import 'dart:async';
void main() async {
print('START');
futureOrExample();
print('END');
}
void futureOrExample() async {
FutureOr<int> futureOr = 42;
print('before await');
await futureOr;
print('end await');
}
which prints:
START
before await
END
end await
when I would expect:
START
before await
end await
END
In that case, why does FutureOr (or more generally await 42) work this way?
Similarly, what's the purpose of FutureOr in that situation since it produces the same result as Future?
I know that I could use SynchronousFuture to achieve the desired result, but I'm just trying to understand what's the use of FutureOr.
The use of FutureOr, as introduced with Dart 2, is to allow you to provide either a value or a future at a point where the existing Dart 1 API allowed the same thing for convenience, only in a way that can be statically typed.
The canonical example is Future.then. The signature on Future<T> is Future<R> then<R>(FutureOr<R> action(T value), {Function onError}).
The idea is that you can have an action on the future's value which is either synchronous or asynchronous. Originally there was a then function which took a synchronous callback and a chain function which took an asynchronous callback, but that was highly annoying to work with, and in good Dart 1 style, the API was reduced to one then method which took a function returning dynamic, and then it checked whether it was a future or not.
In Dart 1 it was easy to allow you to return either a value or a future. Dart 2 was not as lenient, so the FutureOr type was introduced to allow the existing API to keep working. If we had written the API from scratch, we'd probably have done something else, but migrating the existing asynchronous code base to something completely different was not an option, so the FutureOr type was introduced as a type-level hack.
The await operation was also originally defined to work on any object, long before FutureOr existed. For consistency and smaller code, an await e where e evaluated to a non-future would wrap that value in a future and await that. It means that there is only one quick and reusable check on a value (is it a future, if not wrap it), and then the remaining code is the same. There is only one code-path.
If the await worked synchronously on non-Future values, there would have to be a synchronous code path running through the await, as well as an asynchronous path waiting for a future. That would potentially double the code size, for example when compiling to JavaScript (or worse, if there were more awaits in the same control flow, you could get exponential blow-up for a naive implementation). Even if you avoided that by just calling the continuation function synchronously, it would likely be confusing to some readers that an await would not introduce an asynchronous gap. A mistake around that can cause race conditions or things happening in the wrong order.
So, the original design, predating FutureOr, was to make all await operations actually wait.
The introduction of FutureOr did not change this reasoning, and even if it did, it would now be a breaking change to not wait in places where people expect their code to actually give time for other microtasks to run.
The await keyword always lock the function execution.
Writing:
await 42
Is equivalent to:
await Future.value(42)
The reason being:
This is how await works in Javascript
it makes the behavior of await consistent.
Now, what's the purpose of FutureOr then?
FutureOr was never intended as a way to potentially make await synchronous.
Instead, it is an implementation detail of Future.
Without FutureOr, writing the following would not compile:
Future(() {
return 42; // compile error, not a Future
});
Future<int> future;
future.then((value) {
return value * 2; // compile error, not a Future
});
Instead, we would have to wrap all values in a Future.value like so:
Future<int> future;
future.then((value) {
return Future.value(value * 2);
});
for those who are still confused
I found good explanation https://itnext.io/what-is-futureor-in-dart-flutter-681091162c57 without diving into details
This piece of code could explain the target and real use cases of FutureOr
abstract class IDBService {
FutureOr<String> fetch();
}
class FirebaseRemoteService extends IDBService {
#override
Future<String> fetch() async => await 'data';
}
class LocalHiveDbService extends IDBService {
#override
String fetch() => 'data';
}
so in implementations of IDBService
the return type can be Future or String at the same time now!
Coming late to the discussion.
Updating my Dart comprehension - pardon my C++/JS -ish approach.
Seems like this would be useful for singleton initiation. Consider following:
import 'dart:async';
class AClass {
static String _info = '';
static FutureOr<String> get info async {
if (_info.isEmpty) {
print('--> is empty...');
_info = await Future.delayed(Duration(seconds:2),
() => "I'm alive!!");
}
else {
print('--> not empty');
}
return _info;
}
}
Future<void> main() async {
String info = await AClass.info;
print('Fist call: ' + info);
info = await AClass.info;
print('Second call: ' + info);
}
It works as expected - in either case, whether the _info member has been instantiated or not, the getter returns a valid string.
It works fine if I just use a Future<String> specifier in the getter, too. The current implementation makes FutureOr seem mostly like a self-documentation exercise (can return a Future<String> or a String...)
But, even if await currently always locks the execution, a future update may allow it to work as expected, in which case using the FutureOr construct would anticipate updates.
(Aside: I imagine this example could be condensed using an Optional wrapping the _info member, but that's a different exercise...)
I needed to use FutureOr today. I wanted to call a function that might be asynchronously (not always).
String callbackOne() => "hello";
Future<String> callbackTwo() async => (await Future.delayed(Duration(seconds: 1),() => "This is a sentence"));
Problem
I can do getLengthOfResult(callbackOne), but not getLengthOfResult(callbackTwo). Conversely, if accept an async callback, I can't use the sync callback.
DartPad
Future<int> getLengthOfResult(String Function() callback) async {
return callback().length;
}
Solution
DartPad
Future<int> getLengthOfResult(FutureOr<String> Function() callback) async {
// I can await on callbackOne, even though it returns a String.
final result = await callback();
return result.length;
}
main() {
getLengthOfResult(callbackOne);
getLengthOfResult(callbackTwo);
}